Java 帝国之Java bean (上)
转自: 刘欣 码农翻身 2016-05-27
前言: 最近看到到spring 的bean 配置, 突然想到可能很多人不一定知道这个叫bean的东西的来龙去脉, 所以就写个文章来讲一下。
另外,上次出了开源中国抄袭事件, 为了防止转载以后我的公众号信息被故意删除, 我在文章的内容中加上了一些 刘欣(微信公众号:码农翻身) 这样的字样, 可能会造成一些烦扰, 请见谅。
我一手创立的Java帝国刚刚成立不久,便受到巨大的打击, 我派出去占领桌面开发的部队几乎全军覆没。
情报说微软的Visual Basic 和Borland的Delphi最近在宣传什么组件化开发, 难道就是这东西把我们搞垮了?
刘欣(微信公众号:码农翻身)注:参见《Java :一个帝国的诞生》和《Basic: 一个老兵的自述》
我赶紧买了一个Visual Basic 过来研究, 果然,这个家伙确实是方便, 最让我惊叹的是:它有一个可视化编辑器 !
我只需要把一个组件(例如按钮)拖拽到可一个表单上, 设置一下属性 (颜色,字体), 再添加一个事件(onClick), 最后在onClick中写点代码就搞定了 !
不仅如此,我自己也可以把我的代码按规范包装成一个组件, 发布出去让别人使用。
我看着手下给我买来的《程序员大本营》光盘, 里边竟然包含了好几千个这样的组件, 有数据库浏览组件, 计时器组件, 颜色选取组件, 甚至还有收发邮件的组件......
天哪, 这以后开发桌面程序岂不太简单了 !
怪不得我的Java 被打得满地找牙!
刘欣(微信公众号:码农翻身)注: 90年代末的程序员和学生估计都知道《程序员大本营》, 由csdn的创始人蒋涛制作。
我赶紧打电话给我的干将小码哥 : 小码啊, 你赶紧看看这个Visual Basic 和Delphi , 给你7天时间, 我们Java 也得赶紧搞一套这样的东西出来。
小吗毫不含糊, 三天就给我搞了一个东西出来: Java Bean API 规范 。
我翻开一看, 哇塞, 长达114页 , 于是问他:“这是什么东西? 我要的可视化编辑器呢Visual Java 呢? ”
刘欣(微信公众号:码农翻身)注: 我下载浏览了java bean 的规范, 确实是114页
他说: “老大, 我们是个开源的社区, 得充分利用大家的力量, 所以我没有去做像VB和Delphi那样的东西, 相反,我定义了一套规范, 只要大家按照这个规范做, 谁都可以用java 做出像VB那样的可视化开发工具出来。”
“那你说说这个java bean 到底是什么规范?”我问。
“首先,一个java bean 其实就是一个普通的java 类, 但我们对这个类有些要求:
1. 这个类需要是public 的, 然后需要有个无参数的构造函数
2. 这个类的属性应该是private 的, 通过setXXX()和getXXX()来访问
3. 这个类需要能支持“事件”, 例如addXXXXListener(XXXEvent e), 事件可以是Click事件,Keyboard事件等等, 当然咱们也支持自定义的事件。
4. 我们得提供一个所谓的自省/反射机制, 这样能在运行时查看java bean 的各种信息“
5. 这个类应该是可以序列化的, 即可以把bean的状态保存的硬盘上, 以便以后来恢复。
“这些要求看起来也没啥啊,对程序员来说,不就是个普通的java 类吗? 到底该怎么用? ”
“我们幻想一下,假设我们的Java bean 大行其道了, 有个用户在用一个Visual Java Builder 这样的可视化开发工具, 当他用这个工具创建应用的时候, 可以选择一个叫JButton的组件, 加到一个表单上, 此时Visual Java Builder 就需要把这JButton的类通过反射给new 出来, 所以就需要一个无参数的构造函数了。”
“如果用户想去设置一下这个JButton的属性,Visual Java Builder 就需要先用自省/反射来获取这个JButton有哪些属性(通过getter/setter), 拿到以后就可以给用户显示一个属性清单了, 例如背景色, 字体 等等。用户看到后就可以设置背景色和字体了, 此时Visual Java Builder 在内部就需要调用这个Bean的setBackgroundCorlor()/setFont() 等方法, 这就是所谓的setXXXX()方法。”
“如果用户想对这个JButton编程, Visual Java Builder 还是通过自省/反射来获取这个JButton有哪些事件, 给用户展示一个事件清单,例如click , keyboardPressed 用户可以选取一个, 然后就可以写程序对这个事件编程了。”
“可是那个序列化有什么用呢?”
“这是因为用户设计完了以后,可能关掉Visual Java Builder 啊 , 如果不通过序列化把设计好的JButton保存起来, 下次再打开Visual Java Builder , 可就什么都没有了”
我想了想, 小码哥设计的不错,仅仅用了一个简单的规范就满足了可视化编辑器的所有要求。
"那我们就发布这个规范吧, 咱们自己先做一个可视化编辑器,给别人做个榜样, 名称我都想好了, 叫NetBean吧。"
刘欣(微信公众号:码农翻身)注:这是我杜撰的, 实际上NetBean这个名称可能和java bean 并没有实际关联。
果然不出我们所料, Java bean 发布以后, 有力的带动了Java 的IDE市场, 开发Delphi的Borland公司 也来插了一脚,搞出了一个JBuilder, 风靡一时。
IBM 搞了一个Visual Age for Java , 后来摇身一变, 成了一个叫Eclipse的开放平台,超级受大家欢迎, 它反过头来把我们的Netbean 和 JBuilder 逼的快没有活路了。
虽然我们玩的很欢,但是程序员根本不买账, Java 在桌面开发市场还是没有起色,使用Java bean 创建桌面程序的程序员少之又少, 只有部分像金融、ERP这样的领地还在坚持。
看来是无药可救了。
但是Java bean 何去何从 ? 丢弃掉太可惜了, 我和小码哥商量了一下, 我们觉得:既然我们Java在统治了服务器端的编程, 还是在那里想想办法吧......
上一篇提到Java bean 的规范虽然定义的不错, 但却没有获得意料中的成功, 尤其是Java帝国所期待的桌面开发组件化市场上。
我和小码哥多么期待CSDN也能出一期《程序员大本营》, 里边包含成千上万的java bean 组件啊。
不要幻想了, 赶紧把java bean 应用在服务器端才是正事。
JSP + Java Bean
小码哥建议先用在jsp上试试, 可以用java bean 来封装业务逻辑,保存数据到数据库, 像这样:
(微信公众号"码农翻身"注: 这其实叫做JSP Model 1 )
其中jsp 直接用来接受用户的请求, 然后通过java bean 来处理业务, 具体的使用方法是:
这就能把HTTP request中的所有参数都设置到 user 这个java bean 对应的属性上去。
如果想偷懒, 还可以这样:
当然要保证 http request中的参数名和 java bean 中的属性名是一样的。
这个叫做JSP Model 1 的模型受到了很多Java程序员的欢迎 , 因为他们的应用规模都很小, 用Model 1 使得开发很快速。
实际上, 这种方式和微软帝国的asp , 以及和开源的php 几乎一样。
但很快就有人来找我抱怨了, 说他们的项目中使用了Model 1 导致整个系统的崩溃。
他说: “你知道吗? 我们的系统中有好几千个jsp, 这些jsp互相调用(通过GET/POST), 到了最后调用关系无人能搞懂。 ”
其实我们已经预料到了, 小码哥对此有个形象的比喻:意大利面条
这几千个JSP 就像这碗面条一样,搅在一起, 根本理不清楚。
为了解决这个问题,小码哥又推出了 :JSP Model 2 , 这是个模型真正的体现了Model-View-Controller的思想:
Servlet 充当Controller , jsp 充当 View
Java bean 当然就是Model 了!
业务逻辑, 页面显示, 和处理过程做了很好的分离。
基于这个模型的扩展和改进, 很多Web开发框架开始如雨后春笋一样出现, 其中最著名的就是Struts, SpringMVC 了。
Java Web开发迅速的繁荣了。
我们再一次体会到了开放的好处 !
Enterprise Java bean
但是好景不长, 自从Java帝国统治了所谓的“企业级应用”开发领地, 各种各样的游行和抗议层出不穷:
“我们要分布式”
“我们要安全”
“我们要事务”
“我们要高可用性”
“......”
帝国分析了一下, 其实这些程序员的诉求可以归结为:
“我们只想关注我们的业务逻辑, 我们不想, 也不应该由我们来处理‘低级’的事务, 多线程,连接池,以及其他各种各种的‘低级’API, 此外Java帝国一定得提供集群功能, 这样我们的一台机器死机以后,整个系统还能运转。 ”
我们不能坐着不管, 企业级应用是我们的命根子。
小码哥彻夜工作, 最终拿出了一个叫做J2EE的东西, 像Java bean 一样, 这还是一个规范, 但是比Java bean 复杂的多, 其中有:
JDBC: Java 数据库连接, 没有数据库的支持怎么能叫企业级应用?
JNDI : Java 命名和目录接口, 通过一个名称就可以定位到一个数据源, 连jdbc连接都不用了
RMI: 远程过程调用, 让一个机器上的java 对象可以调用另外一个机器上的java 对象 , 你们不是要分布式吗?
JMS : Java 消息服务, 可以使用消息队列了, 这不是企业级应用非常需要的吗?
JTA: Java 事务管理, 支持分布式事务, 能在访问、更新多个数据库的时候,仍然保证事务, 还是分布式。
Java mail : 收发邮件也是必不可少的啊。
刘欣(微信公众号号:码农翻身)注: J2EE 后来改成了Java EE。
当然还有最最最重要的升级, 小码哥把java bean 变成了 Enterprise Java bean , 简称 EJB。
小码哥宣称:
使用了EJB, 你就可以把精力只放在业务上了, 那些烦人的事务管理, 安全管理,线程 统统交给容器(应用服务器)来处理吧。
我们还提供了额外的福利, 只要你的应用服务器是由多个机器组成的集群, EJB就可以无缝的运行在这个集群上, 你完全不用考虑一个机器死掉了应用该怎么办。我们都帮你搞定了。
使用Session Bean , 可以轻松的处理你的业务。
使用实体Bean (Entity bean ) , 你和数据库打交道会变得极为轻松, 甚至sql 都不用写了。
使用消息驱动Bean(Message Driven bean ) , 你可以轻松的和一个消息队列连接, 处理消息。
听起来很美好是不是?
企业级开发领地的程序员们欢呼雀跃, 坐上了J2EE这条船,似乎一下子高贵起来, 开始鄙视微软的ASP, 开源的PHP, Python 等“不入流”的语言了 。
Weblogic , Websphere等符合J2EE规范的应用服务器趁势而上, 摇旗呐喊, 仿佛一个新的时代要来临了, 当然他们在背后一直闷声发大财。
Sring
有句古话是对的, 捧的越高, 跌的越惨。
很快,大部分的程序员就发现, 美好的前景并没有实现, EJB中用起来极为繁琐和笨重, 性能也不好, 为了获得所谓的分布式,反而背上了沉重的枷锁。
实体Bean很快没人用了, 就连简单的无状态Session bean 也被大家所诟病, 其中一条罪状就是“代码的侵入性”。
也是, 小码哥定义EJB的时候没考虑那么多,程序员在定义一个Session bean的时候,需要写一大堆和业务完全没有关系的类。
还需要被迫实现一些根本不应该实现的接口及其方法:
为了哪怕一点点业务逻辑, 我们都得写这么多无用的代码 ! 程序员们出离愤怒了!
他们发起了一场叫做POJO (Plain Old Java Object)的运动, 高唱这POJO之歌, 要求我们整改。
他们希望这个样子:
public class HelloworldBean{
public String hello(){
return "hello world"
}
}
与此同时,他们还过分的要求保留事务了, 安全了这些必备的东西。
程序员确实不好伺候, 但是我们已经被Weblogic, Websphere这些大佬们“绑架”, 想改动谈何容易 !
2002年, 小码哥看到了Rod Johnson写的一本书《Expert one on one J2EE development withoutEJB》 , 赶紧跑来找我:
“老大, 坏了坏了, 你快看看这本书吧, 这个叫Rod的家伙写的这本书直击我们的命门,这厮要搞without EJB”
(微信公众号"码农翻身"注: 虽然这本书翻译的很差,但由于是spring的开山之作,还是值得读一下, 最好中英文对照)
岂止是without EJB, 他还“偷偷的”推出了一个叫什么Spring的框架, 已经迅速的流行开了。
Spring 框架顺应了POJO的潮流, 提供了一个spring 的容器来管理这些POJO, 好玩的是也叫做bean 。
看来我们的java bean 走了一圈又回到了原点。
对于一个Bean 来说,如果你依赖别的Bean , 只需要声明即可, spring 容器负责把依赖的bean 给“注入进去“, 起初大家称之为控制反转(IoC)
后来 Martin flower 给这种方式起来个更好的名字,叫“依赖注入”。
如果一个Bean 需要一些像事务,日志,安全这样的通用的服务, 也是只需要声明即可, spring 容器在运行时能够动态的“织入”这些服务, 这叫AOP。
后来我和小码哥商量, 我们EJB也学习Spring , 简化开发和配置, 但是为时已晚, Spring 已经成为事实上的标准了!程序员已经被spring 拉走了!
不过令我们欣慰的是, spring和spring mvc极大的增加了我们对web开发领地的统治力, java 帝国更加强盛了。
(全文完)
Spring 的本质系列(1) -- 依赖注入
转自: 刘欣 码农翻身 2016-06-25
转载 码农翻身微信公众号 2016-06-25 刘欣 《Spring 的本质系列(1) -- 依赖注入》
1. 对象的创建
面向对象的编程语言是用类(Class)来对现实世界进行抽象, 在运行时这些类会生成对象(Object)。
当然,单独的一个或几个对象根本没办法完成复杂的业务, 实际的系统是由千千万万个对象组成的, 这些对象需要互相协作才能干活,例如对象A调用对象B的方法,那必然会提出一个问题:对象A怎么才能获得对象B的引用呢?
最简单的办法无非是: 当对象A需要使用对象B的时候, 把它给new 出来 ,这也是最常用的办法, java 不就是这么做的?例如: Apple a = new Apple();
后来业务变复杂了, 抽象出了一个水果(Fruit)的类, 创建对象会变成这个样子:
Fruit f1 = new Apple();
Fruit f2 = new Banana();
Fruit f3 = ......
很自然的,类似下面的代码就会出现:
这样的代码如果散落在各处,维护起来将会痛苦不堪, 例如你新加一个水果的类型Orange, 那得找到系统中所有的这些创建Fruit的地方,进行修改, 这绝对是一场噩梦。
解决办法也很简单, 前辈们早就总结好了:工厂模式
工厂模式,以及其他模式像抽象工厂, Builder模式提供的都是创建对象的方法。这背后体现的都是“封装变化”的思想。这些模式只是一些最佳实践而已: 起了一个名称、描述一下解决的问题、使用的范围和场景,码农们在项目中还得自己去编码实现他们。
2. 解除依赖
我们再来看一个稍微复杂一点, 更加贴近实际项目的例子:
一个订单处理类,它会被定时调用: 查询数据库中订单的处理情况, 必要时给下订单的用户发信。
看起来也没什么难度, 需要注意的是很多类一起协作了, 尤其是OrderProcessor , 它依赖于OrderService 和 EmailService这两个服务,它获取依赖的方式就是通过单例方法。
如果你想对这个process方法进行单元测试--这也是很多优秀的团队要求的-- 麻烦就来了。
首先OrderService 确实会从真正的数据库中取得Order信息,你需要确保数据库中有数据, 数据库连接没问题,实际上如果数据库连接Container(例如Tomcat)管理的, 你没有Tomcat很难建立数据库连接。
其次这个EmailService 真的会对外发邮件, 你可不想对真正的用户发测试邮件,当然你可以修改数据库,把邮件地址改成假的,但那样很麻烦, 并且EmailService 会抛出一堆错误来,很不爽。
所有的这些障碍, 最终会导致脆弱的单元测试: 速度慢, 不可重复,需要手工干预,不能独立运行。
想克服这些障碍, 一个可行的办法就是不在方法中直接调用OrderService和EmailService的getInstance()方法, 而是把他们通过setter方法传进来。
通过这种方式,你的单元测试就可以构造一个假的OrderService 和假的EmailService 了。
例如OrderService 的冒牌货可以是MockOrderService , 它可以返回你想要的任何Order 对象, 而不是从数据库取。MockEmailService 也不会真的发邮件, 而是把代码中试图发的邮件保存下来, 测试程序可以检查是否正确。
你的测试代码可能是这样的:
当然, 有经验的你马上就会意识到: 需要把OrderService 和 EmailService 变成 接口或者抽象类, 这样才可以把Mock对象传进来。
这其实也遵循了面向对象编程的另外一个要求: 对接口编程, 而不是对实现编程。
3. Spring 依赖注入
啰啰嗦嗦说了这么多, 快要和Spring扯上关系了。
上面的代码其实就是实现了一个依赖的注入,把两个冒牌货注入到业务类中(通过set方法), 这个注入的过程是在一个测试类中通过代码完成的。
既然能把冒牌货注入进去, 那毫无疑问,肯定也能把一个正经的类安插进去, 因为setter 方法接受的是接口,而不是具体类。
用这种方式来处理对象之间的依赖, 会强迫你对接口编程, 好处显而易见。
随着系统复杂度的增长, 这样的代码会越来越多, 最后也会变得难于维护。
能不能把各个类之间的依赖关系统一维护呢?
能不能把系统做的更加灵活一点,用声明的方式而不是用代码的方式来描述依赖关系呢?
肯定可以, 在Java 世界里,如果想描述各种逻辑关系, XML是不二之选:
这个xml 挺容易理解的, 但是仅仅有它还不够, 还缺一个解析器(假设叫做XmlAppContext)来解析,处理这个文件,基本过程是:
0. 解析xml, 获取各种元素
1. 通过Java反射把各个bean 的实例创建起来: com.coderising.OrderProcessor , OrderServiceImpl, EmailServiceImpl.
2. 还是通过Java反射调用OrderProcessor的两个方法:setOrderService(....) 和 setEmailService(...) 把orderService , emailService 实例 注入进去。
应用程序使用起来就简单了:
XmlAppContext ctx = new XmlAppContext("c:\\bean.xml");
OrderProcessor op = (OrderProcessor) ctx.getBean("order-processor");
op.process();
其实Spring的处理方式和上面说的非常类似, 当然Spring 处理了更多的细节,例如不仅仅是setter方法注入, 还可以构造函数注入,init 方法, destroy方法等等, 基本思想是一致的。
既然对象的创建过程和装配过程都是Spring做的, 那Spring 在这个过程中就可以玩很多把戏了, 比如对你的业务类做点字节码级别的增强, 搞点AOP什么的, 这都不在话下了。
4. IoC vs DI
“不要给我们打电话,我们会打给你的(don‘t call us, we‘ll call you)”这是著名的好莱坞原则。
在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项目完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。
这和软件开发有一定的相似性, 演员们就像一个个Java Object, 最早的时候自己去创建自己所依赖的对象, 有了演艺公司(Spring容器)的介入,所有的依赖关系都是演艺公司搞定的, 于是控制就翻转了
Inversion of Control, 简称IoC。 但是IoC这个词不能让人更加直观和清晰的理解背后所代表的含义, 于是Martin Flower先生就创造了一个新词 : 依赖注入 (Dependency Injection,简称DI), 是不是更加贴切一点?
Spring本质系列(2)-AOP
原创: 刘欣 码农翻身 2016-06-30
据说有些词汇非常热门和神奇, 如果你经常把它挂在嘴边,就能让自己功力大涨, 可以轻松找到理想的高薪的工作,这些词就包括上一篇文章(《Spring本质系列(1) --依赖注入》)中聊过的IoC 和 DI, 也包括今天要聊的AOP。
AOP(Aspect Oriented Programming)就是面向切面的编程, 为什么是面向切面, 而不是面向对象呢?
1. 问题来源
我们在做系统设计的时候,一个非常重要的工作就是把一个大系统做分解, 按业务功能分解成一个个低耦合、高内聚的模块,就像这样:
但是分解以后就会发现有些很有趣的东西, 这些东西是通用的,或者是跨越多个模块的:
日志: 对特定的操作输出日志来记录
安全:在执行操作之前进行操作检查
性能:要统计每个方法的执行时间
事务:方法开始之前要开始事务, 结束后要提交或者回滚事务
等等....
这些可以称为是非功能需求, 但他们是多个业务模块都需要的, 是跨越模块的, 把他们放到什么地方呢?
最简单的办法就是把这些通用模块的接口写好, 让程序员在实现业务模块的时候去调用就可以了,码农嘛,辛苦一下也没什么。
这样做看起来没问题, 只是会产生类似这样的代码:
这样的代码也实现了功能,但是看起来非常的不爽, 那就是日志,性能,事务 相关的代码几乎要把真正的业务代码给淹没了。
不仅仅这一个类需要这么干, 其他类都得这么干, 重复代码会非常的多。
有经验的程序员还好, 新手忘记写这样的非业务代码简直是必然的。
2. 设计模式:模板方法
用设计模式在某些情况下可以部分解决上面的问题,例如著名的模板方法:
在父类(BaseCommand)中已经把那些“乱七八糟“的非功能代码都写好了, 只是留了一个口子(抽象方法doBusiness())让子类去实现。
子类变的清爽, 只需要关注业务逻辑就可以了。
调用也很简单,例如:
BaseCommand cmd = ... 获得PlaceOrderCommand的实例...
cmd.execute();
但是这样方式的巨大缺陷就是父类会定义一切: 要执行哪些非功能代码, 以什么顺序执行等等
子类只能无条件接受,完全没有反抗余地。
如果有个子类, 根本不需要事务, 但是它也没有办法把事务代码去掉。
3. 设计模式:装饰者
如果利用装饰者模式, 针对上面的问题,可以带来更大的灵活性:
现在让这个PlaceOrderCommand 能够打印日志,进行性能统计
Command cmd = new LoggerDecorator(
new PerformanceDecorator(
new PlaceOrderCommand()));
cmd.execute();
如果PaymentCommand 只需要打印日志,装饰一次就可以了:
Command cmd = new LoggerDecorator(
new PaymentCommand());
cmd.execute();
可以使用任意数量装饰器,还可以以任意次序执行(严格意义上来说是不行的), 是不是很灵活?
4. AOP
如果仔细思考一下就会发现装饰者模式的不爽之处:
(1) 一个处理日志/性能/事务 的类为什么要实现 业务接口(Command)呢?
(2) 如果别的业务模块,没有实现Command接口,但是也想利用日志/性能/事务等功能,该怎么办呢?
最好把日志/安全/事务这样的代码和业务代码完全隔离开来,因为他们的关注点和业务代码的关注点完全不同 ,他们之间应该是正交的,他们之间的关系应该是这样的:
如果把这个业务功能看成一层层面包的话, 这些日志/安全/事务 像不像一个个“切面”(Aspect) ?
如果我们能让这些“切面“能和业务独立, 并且能够非常灵活的“织入”到业务方法中, 那就实现了面向切面编程(AOP)!
5. 实现AOP
现在我们来实现AOP吧, 首先我们得有一个所谓的“切面“类(Aspect), 这应该是一个普通的java 类,不用实现什么“乱七八糟”的接口。以一个事务类为例:
我们想达到的目的只这样的: 对于com.coderising这个包中所有类的execute方法, 在方法调用之前,需要执行Transaction.beginTx()方法, 在调用之后, 需要执行Transaction.commitTx()方法。
暂时停下脚步分析一下。
“对于com.coderising这个包中所有类的execute方法” , 用一个时髦的词来描述就是切入点(PointCut) , 它可以是一个方法或一组方法(可以通过通配符来支持,你懂的)
”在方法调用之前/之后 , 需要执行xxx“ , 用另外一个时髦的词来描述就是通知(Advice)
码农翻身认为,PointCut,Advice 这些词实在是不直观, 其实Spring的作者们也是这么想的 : These terms are not Spring-specific… unfortunately, AOP terminology is not particularly intuitive; however, it would be even more confusing if Spring used its own terminology.
当然,想描述这些规则, xml依然是不二之选:
注意:现在Transaction这个类和业务类在源代码层次上没有一点关系,完全隔离了。隔离是一件好事情, 但是马上给我们带来了大麻烦 。
Java 是一门静态的强类型语言, 代码一旦写好, 编译成java class 以后 ,可以在运行时通过反射(Reflection)来查看类的信息, 但是想对类进行修改非常困难。
而AOP要求的恰恰就是在不改变业务类的源代码(其实大部分情况下你也拿不到)的情况下, 修改业务类的方法, 进行功能的增强,就像上面给所有的业务类增加事务支持。
为了突破这个限制,大家可以说是费尽心机, 现在基本是有这么几种技术:
(1) 在编译的时候, 根据AOP的配置信息,悄悄的把日志,安全,事务等“切面”代码 和业务类编译到一起去。
(2) 在运行期,业务类加载以后, 通过Java动态代理技术为业务类生产一个代理类, 把“切面”代码放到代理类中, Java 动态代理要求业务类需要实现接口才行。
(3) 在运行期, 业务类加载以后, 动态的使用字节码构建一个业务类的子类,将“切面”逻辑加入到子类当中去, CGLIB就是这么做的。
Spring采用的就是(1) +(2) 的方式,限于篇幅,这里不再展开各种技术了, 不管使用哪一种方式, 在运行时,真正干活的“业务类”其实已经不是原来单纯的业务类了, 它们被AOP了 !、