AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。然而殊途同归,实现AOP的技术特性却是相同的,分别为:
1、join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。
2、point cut(切入点):本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。
3、advice(通知):是point cut的执行代码,是执行“方面”的具体逻辑。
4、aspect(方面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。
5、introduce(引入):为对象引入附加的方法或属性,从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。
上述的技术特性组成了基本的AOP技术,大多数AOP工具均实现了这些技术。它们也可以是研究AOP技术的基本术语。
2.2.2 横切技术
“横切”是AOP的专有名词。它是一种蕴含强大力量的相对简单的设计和编程技术,尤其是用于建立松散耦合的、可扩展的企业系统时。横切技术可以使得AOP在一个给定的编程模型中穿越既定的职责部分(比如日志记录和性能优化)的操作。
如果不使用横切技术,软件开发是怎样的情形呢?在传统的程序中,由于横切行为的实现是分散的,开发人员很难对这些行为进行逻辑上的实现或更改。例如,用于日志记录的代码和主要用于其它职责的代码缠绕在一起。根据所解决的问题的复杂程度和作用域的不同,所引起的混乱可大可小。更改一个应用程序的日志记录策略可能涉及数百次编辑——即使可行,这也是个令人头疼的任务。
在AOP中,我们将这些具有公共逻辑的,与其他模块的核心逻辑纠缠在一起的行为称为“横切关注点(Crosscutting Concern)”,因为它跨越了给定编程模型中的典型职责界限。
2.2.2.1 横切关注点
一个关注点(concern)就是一个特定的目的,一块我们感兴趣的区域,一段我们需要的逻辑行为。从技术的角度来说,一个典型的软件系统包含一些核心的关注点和系统级的关注点。举个例子来说,一个信用卡处理系统的核心关注点是借贷/存入处理,而系统级的关注点则是日志、事务完整性、授权、安全及性能问题等,许多关注点——即横切关注点(crosscutting concerns)——会在多个模块中出现。如果使用现有的编程方法,横切关注点会横越多个模块,结果是使系统难以设计、理解、实现和演进。AOP能够比上述方法更好地分离系统关注点,从而提供模块化的横切关注点。
例如一个复杂的系统,它由许多关注点组合实现,如业务逻辑、性能,数据存储、日志和调度信息、授权、安全、线程、错误检查等,还有开发过程中的关注点,如易懂、易维护、易追查、易扩展等,图2.1演示了由不同模块实现的一批关注点组成一个系统。
通过对系统需求和实现的识别,我们可以将模块中的这些关注点分为:核心关注点和横切关注点。对于核心关注点而言,通常来说,实现这些关注点的模块是相互独立的,他们分别完成了系统需要的商业逻辑,这些逻辑与具体的业务需求有关。而对于日志、安全、持久化等关注点而言,他们却是商业逻辑模块所共同需要的,这些逻辑分布于核心关注点的各处。在AOP中,诸如这些模块,都称为横切关注点。应用AOP的横切技术,关键就是要实现对关注点的识别。
如果将整个模块比喻为一个圆柱体,那么关注点识别过程可以用三棱镜法则来形容,穿越三棱镜的光束(指需求),照射到圆柱体各处,获得不同颜色的光束,最后识别出不同的关注点。如图2.2所示:
上图识别出来的关注点中,Business Logic属于核心关注点,它会调用到Security,Logging,Persistence等横切关注点。
public class BusinessLogic
{
public void SomeOperation()
{
//验证安全性;Securtity关注点;
//执行前记录日志;Logging关注点;
DoSomething();
//保存逻辑运算后的数据;Persistence关注点;
//执行结束记录日志;Logging关注点;
}
}
AOP的目的,就是要将诸如Logging之类的横切关注点从BusinessLogic类中分离出来。利用AOP技术,可以对相关的横切关注点封装,形成单独的“aspect”。这就保证了横切关注点的复用。由于BusinessLogic类中不再包含横切关注点的逻辑代码,为达到调用横切关注点的目的,可以利用横切技术,截取BusinessLogic类中相关方法的消息,例如SomeOperation()方法,然后将这些“aspect”织入到该方法中。例如图2.3:
通过利用AOP技术,改变了整个系统的设计方式。在分析系统需求之初,利用AOP的思想,分离出核心关注点和横切关注点。在实现了诸如日志、事务管理、权限控制等横切关注点的通用逻辑后,开发人员就可以专注于核心关注点,将精力投入到解决企业的商业逻辑上来。同时,这些封装好了的横切关注点提供的功能,可以最大限度地复用于商业逻辑的各个部分,既不需要开发人员作特殊的编码,也不会因为修改横切关注点的功能而影响具体的业务功能。
为了建立松散耦合的、可扩展的企业系统,AOP应用到的横切技术,通常分为两种类型:动态横切和静态横切。
2.2.2.2 动态横切
动态横切是通过切入点和连接点在一个方面中创建行为的过程,连接点可以在执行时横向地应用于现有对象。动态横切通常用于帮助向对象层次中的各种方法添加日志记录或身份认证。在很多应用场景中,动态横切技术基本上代表了AOP。
动态横切技术的核心主要包括join point(连接点),point cut(切入点),advice(通知)和aspect(方面)。在前面,我已经概要地介绍了这些术语分别代表的含义。接下来,我将以一个具体的实例来进一步阐述它们在AOP动态横切中实现的意义。
考虑一个电子商务系统,需要对订单进行添加、删除等管理操作。毫无疑问,在实际的应用场景中,这些行为应与权限管理结合,只有获得授权的用户方能够实施这些行为。采用传统的设计方法,其伪代码如下:
public class OrderManager
{
private ArrayList m_Orders;
public OrderManager()
{
m_Orders = new ArrayList();
}
public void AddOrder(Order order)
{
if (permissions.Verify(Permission.ADMIN))
{
m_Orders.Add(order);
}
}
public void RemoveOrder(Order order)
{
if (permissions.Verify(Permission.ADMIN))
{
m_Orders.Remove(order);
}
}
}
同样的,在该电子商务系统中,还需要对商品进行管理,它采用了同样的授权机制:
public class ProductManager
{
private ArrayList m_Products;
public ProductManager()
{
m_Products = new ArrayList();
}
public void AddProduct(Product product)
{
if (permissions.Verify(Permission.ADMIN))
{
m_Products.Add(product);
}
}
public void RemoveProduct(Product product)
{
if (permissions.Verify(Permission.ADMIN))
{
m_Products.Remove(product);
}
}
}
如此以来,在整个电子商务系统中,核心业务包括订单管理和商品管理,它们都需要相同的权限管理,如图2.4所示:
毫无疑问,利用AOP技术,我们可以分离出系统的核心关注点和横切关注点,从横向的角度,截取业务管理行为的内部消息,以达到织入权限管理逻辑的目的。当执行AddOrder()等方法时,系统将验证用户的权限,调用横切关注点逻辑,因此该方法即为AOP的join point。对于电子商务系统而言,每个需要权限验证的方法都是一个单独的join point。由于权限验证将在每个方法执行前执行,所以对于这一系列join point,只需要定义一个point cut。当系统执行到join point处时,将根据定义去查找对应的point cut,然后执行这个横切关注点需要实现的逻辑,即advice。而point cut和advice,就组合成了一个权限管理aspect。
由于aspect是一个封装的对象,我们可以定义这样一个aspect:
private static aspect AuthorizationAspect{……}
然后在这个aspect中定义point cut,在point cut中,定义了需要截取上下文消息的方法,例如:
private pointcut authorizationExecution():
execution(public void OrderManager.AddOrder(Order)) ||
execution(public void OrderManager.DeleteOrder(Order)) ||
execution(public void ProductManager.AddProduct(Product)) ||
execution(public void ProductManager.DeleteProduct(Product));
由于权限验证是在订单管理方法执行之前完成,因此在before advice中,定义权限检查:
before(): authorizationExecution()
{
if !(permissions.Verify(Permission.ADMIN))
{
throw new UnauthorizedException();
}
}
通过定义了这样一个完整的aspect,当系统调用OrderManager或ProductManager的相关方法时,就触发了point cut,然后调用相应的advice逻辑。如此以来,OrderManager和ProductManager模块就与权限管理模块完全解除了依赖关系,同时也消除了传统设计中不可避免的权限判断的重复代码。这对于建立一个松散耦合、可扩展的系统软件是非常有利的。
2.2.2.3 静态横切
静态横切和动态横切的区别在于它不修改一个给定对象的执行行为。相反,它允许通过引入附加的方法字段和属性来修改对象的结构。此外,静态横切可以把扩展和实现附加到对象的基本结构中。在AOP实现中,通常将静态横切称为introduce或者mixin。
静态横切在AOP技术中,受到的关注相对较少。事实上,这一技术蕴含的潜力是巨大的。使用静态横切,架构师和设计者能用一种真正面向对象的方法有效地建立复杂系统的模型。静态横切允许您不用创建很深的层次结构,以一种本质上更优雅、更逼真于现实结构的方式,插入跨越整个系统的公共行为。尤其是当开发应用系统时,如果需要在不修改原有代码的前提下,引入第三方产品和API库,则静态横切技术将发挥巨大的作用。
举例来说,当前已经实现了一个邮件收发系统,其中类Mail完成了收发邮件的功能。但在产品交付后,发现该系统存在缺陷,在收发邮件时,未曾实现邮件地址的验证功能。现在,第三方产品已经提供了验证功能的接口IValidatable:
public interface IValidatable
{
bool ValidateAddress();
}
我们可以利用设计模式中的Adapter模式,来完成对第三方产品API的调用。我们可以定义一个新的类MailAdapter,该类实现了IValidatable接口,同时继承了Mail类:
public class MailAdapter:Mail,IValidatable
{
public bool ValidateAddress()
{
if(this.getToAddress() != null)
{
return true;
}
else
{
return false;
}
}
}
通过引入MailAdapter类,原来Mail对象完成的操作,将全部被MailAdapter对象取代。然而,此种实现方式虽然能解决引入新接口的问题,但类似下面的代码,却是无法编译通过的:
Mail mail = new Mail();
IValidatable validate = ((IValidatable)mail).ValidateAddress();
必须将第一行代码作如下修改:
Mail mail = new MailAdapter();
利用AOP的静态横切技术,可以将IValidatable接口织入到原有的Mail类中,这是一种非常形象的introduce功能,其实现仍然是在aspect中完成:
import com.acme.validate.Validatable;
public aspect MailValidateAspect
{
declare parents: Mail implements IValidatable;
public boolean Mail.validateAddress()
{
if(this.getToAddress() != null)
{
return true;
}
else
{
return false;
}
}
}
静态横切的方法,并没有引入类似MailAdapter的新类,而是通过定义的MailValidateAspect方面,利用横切技术为Mail类introduce了新的方法ValidateAddress(),从而实现了Mail的扩展。因此如下的代码完全可行。
Mail mail = new Mail();
IValidatable validate = ((IValidatable)mail).ValidateAddress();
2.3 AOP技术的优势
AOP技术的优势是显而易见的。在面向对象的世界里,人们提出了各种方法和设计原则来保障系统的可复用性与可扩展性,以期建立一个松散耦合、便于扩展的软件系统。例如GOF提出的“设计模式”,为我们提供了设计的典范与准则。设计模式通过最大程度的利用面向对象的特性,诸如利用继承、多态,对责任进行分离、对依赖进行倒置,面向抽象,面向接口,最终设计出灵活、可扩展、可重用的类库、组件,乃至于整个系统的架构。在设计的过程中,通过各种模式体现对象的行为、暴露的接口、对象间关系、以及对象分别在不同层次中表现出来的形态。然而鉴于对象封装的特殊性,“设计模式”的触角始终在接口与抽象中大做文章,而对于对象内部则无能为力。
通过“横切”技术,AOP技术就能深入到对象内部翻云覆雨,截取方法之间传递的消息为我所用。由于将核心关注点与横切关注点完全隔离,使得我们能够独立的对“方面”编程。它允许开发者动态地修改静态的OO模型,构造出一个能够不断增长以满足新增需求的系统,就象现实世界中的对象会在其生命周期中不断改变自身,应用程序也可以在发展中拥有新的功能。
设计软件系统时应用AOP技术,其优势在于:
(一)在定义应用程序对某种服务(例如日志)的所有需求的时候。通过识别关注点,使得该服务能够被更好的定义,更好的被编写代码,并获得更多的功能。这种方式还能够处理在代码涉及到多个功能的时候所出现的问题,例如改变某一个功能可能会影响到其它的功能,在AOP中把这样的麻烦称之为“纠结(tangling)”。
(二)利用AOP技术对离散的方面进行的分析将有助于为开发团队指定一位精于该项工作的专家。负责这项工作的最佳人选将可以有效利用自己的相关技能和经验。
(三)持久性。标准的面向对象的项目开发中,不同的开发人员通常会为某项服务编写相同的代码,例如日志记录。随后他们会在自己的实施中分别对日志进行处理以满足不同单个对象的需求。而通过创建一段单独的代码片段,AOP提供了解决这一问题的持久简单的方案,这一方案强调了未来功能的重用性和易维护性:不需要在整个应用程序中一遍遍重新编写日志代码,AOP使得仅仅编写日志方面(logging aspect)成为可能,并且可以在这之上为整个应用程序提供新的功能。
总而言之,AOP技术的优势使得需要编写的代码量大大缩减,节省了时间,控制了开发成本。同时也使得开发人员可以集中关注于系统的核心商业逻辑。此外,它更利于创建松散耦合、可复用与可扩展的大型软件系统。
参考连接:http://wayfarer.cnblogs.com/articles/241012.html
http://www.cnblogs.com/zhenyulu/zhenyulu/articles/234074.html
______________________________________________________________________________________________
·work/training/jsp/jspsyntax
Spring AOP
1. 代理模式
1.1. 静态代理
程序中经常需要为某些动作或事件作下记录,以便在事后检测或作为排错的依据,先看一个简单的例子:
import java.util.logging.*;
public class HelloSpeaker {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void hello(String name) {
logger.log(Level.INFO, "hello method starts....");
System.out.println("Hello, " + name);
logger.log(Level.INFO, "hello method ends....");
}
}
HelloSpeaker在执行hello方法时,我们希望能记录该方法已经执行及结束,最简单的做法就是在执行前后加上记录动作,然而Logger介入了HelloSpeaker中,记录这个动作并不属于HelloSpeaker,这使得HelloSpeaker的职责加重。
如果程序中这种记录的动作到处都有需求,上面的这种写法势必造成我们必须复制记录动作的程序代码,使得记录动作的难度加大。
通过静态代理可以适当的解决上面出现的问题。
例如:
1) 定义一接口:
public interface IHello {
public void hello(String name);
}
2) 定义一实现类
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
}
3) 定义代理类:
import java.util.logging.*;
public class HelloProxy implements IHello {
private Logger logger = Logger.getLogger(this.getClass().getName());
private IHello helloObject;
public HelloProxy(IHello helloObject) {
this.helloObject = helloObject;
}
public void hello(String name) {
logger.log(Level.INFO, "hello method starts....");
helloObject.hello(name);
logger.log(Level.INFO, "hello method ends....");
}
}
4) 定义测试类:
public class TestStaticProxy {
public static void main(String[] args){
IHello helloProxy = new HelloProxy(new HelloSpeaker());
helloProxy.hello("Justin");
}
}
代理类HelloProxy将代理真正的HelloSpeaker来执行hello(),并在其后加上记录的动作,这使得我们的HelloSpeaker在撰写时不必介入记录动作,HelloSpeaker可以专心於它的职责。
1.2. 动态代理
通过学习静态代理的基本范例,我们可以了解到,代理物件的一个界面只服务于一种类型的物件,而且如果要代理的方法很多,我们必须为每个方法进行代理,静态代理在程式规模稍大时就必定无法胜任。
Java在JDK1.3之后动态代理的引入很好的解决了这个问题,一个handler服务于各个物件,前提是这个个hander必须实现java.lang.reflect.InvocationHandler这个接口.
例如:
1) 定义一接口:
public interface IHello {
public void hello(String name);
}
2) 定义一实现类
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
}
3) 定义动态代理类
import java.util.logging.*;
import java.lang.reflect.*;
public class LogHandler implements InvocationHandler {
private Logger logger = Logger.getLogger(this.getClass().getName());
private Object delegate; //定义目标对象
public Object bind(Object delegate) { //方法是要返回代理对象,参数代表要传过来的目标对象。
this.delegate = delegate;
return Proxy.newProxyInstance( 返回代理类
delegate.getClass().getClassLoader(),//返回类装载器
delegate.getClass().getInterfaces(),返回类所实现的接口
this);//代表实现了InvocationHandler接口的一个对象,这里传入的是自身
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
try {
logger.log(Level.INFO, "method starts..." + method);
result = method.invoke(delegate, args);
logger.log(Level.INFO, "method ends..." + method);
} catch (Exception e){
logger.log(Level.INFO, e.toString());
}
return result;
}
}
Proxy为动态代理类,是动态代理的核心类,其作用类似于代理模式中的代理。Proxy类中包含的全是静态的方法和成员变量,这是一个纯粹的工具类。
java.lang.reflect.Proxy.newProxyInstance方法根据传入的接口类型(obj.getClass().getInterfaces())动态构造一个代理类实例返回,这个代理类是JVM。
在内存中动态构造的动态类,它实现了传入的接口列表中所包含的所有接口。
程序调用代理的目标方法时,自动调用invoke方法,该处理类并未与任何接口或类耦合,完全是通用的,它的目标实例是object类型,可以是任何类型。
invoke方法中第一个参数是指具体的被代理类,即代理模式中的目标对象;method是被代理的方法,args为该方法的参数数组。
4) 定义测试类
public class TestDynamicProxy {
public static void main(String[] args){
LogHandler logHandler = new LogHandler();
IHello helloProxy = (IHello) logHandler.bind(new HelloSpeaker());
helloProxy.hello("Justin");}
}
LogHandler不在服务于特定物件,而HelloSpeaker也不用插入任何有关于记录的动作,它不用意识到记录动作的存在。
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到一个集中的方法中处理(invoke).
Dynamic Proxy要求被代理的必须是接口的实现类,否则无法为其构造相应的动态类。因此,Spring对接口实现类采用DynamicProxy实现AOP,而对没有实现任何接口的类,则通过CGLIB实现AOP代理。
2. AOP基础
需求
· 为ProductDaoImpl的save方法增加授权检查功能
· 为ProductDaoImpl的save方法增加事务的处理功能
· 为ProductDaoImpl的save方法增加打开关闭连接的功能
2.1. 基本概念
AOP(Aspect Orient Programming),面向切面编程,作为面向对象编程(oop)的一种补充,面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面。
oop对业务处理过程中的实体及其属性和行为进行了抽象封装,以获得更加清晰高效的逻辑划分,研究的是一种”静态的”领域。
Aop则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,研究的是一种“动态的”领域。
可以这样理解,面向对象编程是从静态角度考虑程序结构,而面向切面编程是从动态角度考虑程序运行过程。
Spring AOP是Spring框架的一个重要组件,极好的补充了Spring IOC容器的功能。Spring AOP将Spring IOC容器与AOP组件紧密结合,丰富了IOC容器的功能。
Aop的关键是发现横切性问题,最后把它模块化。这个模块化的类通常称为Aspect.
Spring缺省使用j2se动态代理(dynamic proxies)来作为AOP代理。Spring也支持使用CGLIB代理,对于需要代理类而不是代理接口的时候CGLIB是很有必要的。如果一个业务对象并没有实现一个接口,默认会使用CGLIB。作为面向对象的最佳实践,业务对象都会实现一个或多个接口。
2.2. AOP术语
1) 切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect 注解(@AspectJ风格)来实现。
2) 连接点(Joinpoint): 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。 在Spring AOP中,一个连接点 总是 代表一个方法的执行。 通过声明一个org.aspectj.lang.JoinPoint类型的参数可以使通知(Advice)的主体部分获得连接点信息。
3) 通知(Advice): 在切面的某个特定的连接点(Joinpoint)上执行的处理动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。。
4) 切入点(Pointcut): 一系列连接点的集合,它指明处理方式(Advice)将在何时被触发。
5) 引入(Introduction): (也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。 Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。 例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
6) 目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(advised) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
7) AOP代理(AOP Proxy): AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
8) 织入(Weaving): 把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。 这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。 Spring和其他纯Java AOP框架一样,在运行时完成织入。
通知的类型:
1) 前置通知(Before advice): 在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
2) 返回后通知(After returning advice): 在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
3) 抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。
4) 后通知(After (finally) advice): 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
5) 环绕通知(Around Advice): 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
2.3. AOP代理
所谓的AOP代理,就是AOP框架动态创建的对象,这个对象可以作为目标对象的替代品,而AOP代理提供比目标对象更加强大的功能。
Spring默认使用JDK动态代理实现AOP代理,主要用于代理接口。也可以使用CGLIB代理。实现类的代理,而不是接口。如果业务对象没有实现接口,默认使用CGLIB代理。
第一个Spring AOP程式
1) 定义接口:
package com.sinoest;
public interface IHello {
public void hello(String name);
public void morning(String name);
}
2) 定义接口实现类:
package com.sinoest;
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
public void morning(String name) {
System.out.println("Morning, " + name);
}
}
3) 定义代理类:
Aopalliance.jar
package com.sinoest;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.util.logging.*;
public class LogInterceptor implements MethodInterceptor {
private Logger logger = Logger.getLogger(this.getClass().getName());
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
logger.log(Level.INFO, "method starts..." + methodInvocation.getMethod());
try {
Object result = methodInvocation.proceed();
return result;
}
finally {
logger.log(Level.INFO, "method ends..." + methodInvocation.getMethod() + "\n");
}
}
}
Invoke()中的MethodInvocation参数包括了下一个Interceptor的信息、被执行的方法、以及方法所需要的参数。
4) 编写Spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="logInterceptor" class="com.sinoest.LogInterceptor"/>
<bean id="helloSpeaker" class="com.sinoest.HelloSpeaker"/>
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.sinoest.IHello</value>
</property>
<property name="target">
<ref bean="helloSpeaker"/>
</property>
<property name="interceptorNames">
<list>
<value>logInterceptor</value>
</list>
</property>
</bean>
</beans>
proxyInterfaces属性设定所要代理的介面,如果没有设定这个属性,则会基于targat属性所设定的bean来自动检测介面,基本上要代理一个类别所有的方法也是可能的,target属性则是我们要代理的目标物件,而interceptorNames属性用来设定advices,也可以是advisors,其中interceptorNames中设定的顺序就是advices执行的顺序,所有advices执行完后,会执行target物件上的方法。
interceptorNames中所设定的advices会套用至target物件上所代理的所有方法,如果您希望只套用在某些方法上,可以使用
org.springframework.aop.support.RegexpMethodPointcutAdvisor。
例如:希望执行target物件上的hello()方法时才介入interceptor.则可以如下设定。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="logInterceptor" class="com.sinoest.LogInterceptor"/>
<bean id="helloSpeaker" class="com.sinoest.HelloSpeaker"/>
<bean id="regexpMethodPointcutAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="logInterceptor"/>
</property>
<property name="patterns">
<value>.*hello</value>
</property>
</bean>
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.sinoest.IHello</value>
</property>
<property name="target">
<ref bean="helloSpeaker"/>
</property>
<property name="interceptorNames">
<list>
<value>regexpMethodPointcutAdvisor</value>
</list>
</property>
</bean>
</beans>
5) 编写测试类
package com.sinoest;
import org.springframework.context.*;
import org.springframework.context.support.*;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");;
IHello helloProxy = (IHello) context.getBean("helloProxy");
helloProxy.hello("Justin");
helloProxy.morning("momor");
}
}
2.4. Spring通知(Advice)
Advice类型(advice type)有Around、Before、Throws、After Returning。这些Advice类型会分别在以下的时间被调用:在JoinPoint前后、JointPoint前、JointPoint丢出例外时、JointPoint执行完后。
² Interception Around通知
在上一个例子中,我们在hello()这个JointPoint上会调用ArountdAdvice. 即我们的LogHander.
² Before Advice 或 After Returning Advice
Spring中实现 Before Advice 或是 After Returning Advice,作法与Interceptor类似,只要分别实现org.springframework.aop.MethodBeforeAdvice。
及org.springframework.aop.AfterReturningAdvice。两个介面的定义如下:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
MethodBeforeAdvice的before()方法会在被呼叫的方法之前调用。
AfterReturningAdvice的afterReturn()方法会在被呼叫的方法之后调用。
例如:
package com.sinoest;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.logging.*;
public class LogBeforeAdvice implements MethodBeforeAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void before(Method method, Object[] args, Object target)
throws Throwable {
logger.log(Level.INFO, "method starts..." + method);
}
}
package com.sinoest;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
import java.util.logging.*;
public class LogAfterAdvice implements AfterReturningAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void afterReturning(Object object, Method method,
Object[] args, Object target)
throws Throwable {
logger.log(Level.INFO, "method ends..." + method);
}
}
定义配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="logBeforeAdvice" class="com.sinoest.LogBeforeAdvice"/>
<bean id="logAfterAdvice" class="com.sinoest.LogAfterAdvice"/>
<bean id="logBeforeAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="logBeforeAdvice"/>
</property>
<property name="patterns">
<value>com\.sinoest\.IHello\.hello</value>
</property>
</bean>
<bean id="logAfterAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="logAfterAdvice"/>
</property>
<property name="patterns">
<value>com\.sinoest\.IHello\.morning</value>
</property>
</bean>
<bean id="helloSpeaker" class="com.sinoest.HelloSpeaker"/>
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>onlyfun.caterpillar.IHello</value>
</property>
<property name="target">
<ref bean="helloSpeaker"/>
</property>
<property name="interceptorNames">
<list>
<value>logBeforeAdvisor</value>
<value>logAfterAdvisor</value>
</list>
</property>
</bean>
</beans>
这里仍使用了RegexMethodPointcutAdvisor,分别设定在执行hello()之前介入logBeforeAdvisor,以及morning()方法之后使用logAfterAdvisor.
² Throws通知
若想在例外发生时通知作某些事,您可以使用Throws Advice,在Spring中想要使用Throws Advice,必须继承org.springframework.aop.ThrowsAdvice界面。这个界面没有定义任何方法,
您可以在当中定义任意的方法名称,但必须遵循以下的形式:
代码:
methodName([Method], [args], [target], subclassOfThrowable);
Method、args与targer都是可以省略的,方法中一定要的是subclassofThrowable,在例外发生时,会检测所设定的Throws Advice是否有符合例外类型的方法,如果有的就通知它执行。
例如:
定义IHello接口
package com.sinoest;
public interface IHello {
public void hello(String name) throws Exception;
public void morning(String name) throws Exception;
}
定义实现类:
package com.sinoest;
public class HelloSpeaker implements IHello {
public void hello(String name) throws Exception{
System.out.println("Hello, " + name);
}
public void morning(String name) throws Exception {
System.out.println("Morning, " + name);
throw new Exception("exception happened!");
}
}
定义例外处理:
package com.sinoest;
import java.lang.reflect.*;
import java.util.logging.*;
import org.springframework.aop.ThrowsAdvice;
public class CustomExceptionAdvice implements ThrowsAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void afterThrowing(Method method,
Object[] args,
Object target,
Throwable subclass) {
// log the exception
logger.log(Level.INFO,
"Logging that a " +
subclass +
"Exception was thrown in " + method);
}
}
定义配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="customExceptionAdvice" class="onlyfun.caterpillar.CustomExceptionAdvice"/>
<bean id="helloSpeaker" class="onlyfun.caterpillar.HelloSpeaker"/>
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>onlyfun.caterpillar.IHello</value>
</property>
<property name="target">
<ref bean="helloSpeaker"/>
</property>
<property name="interceptorNames">
<list>
<value>customExceptionAdvice</value>
</list>
</property>
</bean>
</beans>
定义测试类:
package com.sinoest;
import org.springframework.context.*;
import org.springframework.context.support.*;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");
IHello helloProxy = (IHello) context.getBean("helloProxy");
try {
helloProxy.hello("Justin");
helloProxy.morning("momor");
}
catch(Exception e) {
// do some exception handling here
}
}
}
² Introduction通知
不常用
2.5. Spring自动代理
Spring AOP 主要是基于动态代理完成的,也就是说每一个要使用Spring AOP完成某个动作的物件,都需要为其建立一个代理,如果您想要为每一个物件的某些(或所有)方法加上记录动作,为每一个物件都建立一个代理物件,是一件很重复且无趣的工作。
使用自动代理可以解决上述问题,自动代理需要使用org.springframework.aop.framework.autoproxy,defaultAdvisorAutoProxyCreator,当呼叫某个Bean时,defaultAdvisorAutoProxyCreator会自动寻找Advisor的类型,然后自动为该物件建立代理。
定义配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="autoProxyCreator"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean id="logInterceptor" class="com.sinoest.LogInterceptor"/>
<bean id="regexpMethodPointcutAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="logInterceptor"/>
</property>
<property name="patterns">
<value>.*hello</value>
</property>
</bean>
<bean id="helloSpeaker" class="onlyfun.caterpillar.HelloSpeaker"/>
</beans>
定义测试类:
package com.sinoest;
import org.springframework.context.*;
import org.springframework.context.support.*;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");
IHello helloSpeaker = (IHello) context.getBean("helloSpeaker");
try {
helloSpeaker.hello("Justin");
helloSpeaker.morning("momor");
}
catch(Exception e) {
// do some exception handling here
}
}
}
3. @AspectJ支持
“@AspectJ”使用了Java 5注解,可以将切面声明为普通的Java类。Spring2.0使用了和AspectJ5一样的注解,使用了AspectJ提供的的一个库来做切点(pointcut)解析和匹配。
3.1. 启用@AspectJ支持
为了在Spring配置中使用@AspectJ aspects,你必须首先启用Spring对基于@AspectJ aspects的配置支持,自动代理(autoproxying)基于通知是否来自这些切面。自动代理是指Spring会判断一个bean是够使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确认通知是否如期举行。
通过在你的Spring的配置中引入下列元素来启用Spring对@AspectJ的支持:
<aop:aspectj-autoproxy/>
启用Spring对@AspectJ支持的前提是需要在你的应用程序的classpath 中引入两个AspectJ库:
aspectjweaver.jar和aspectjrt.jar.
3.2. 声明一个切面
在启用@AspectJ支持的情况下,在application context中定义的任意带有一个@Aspect切面的bean都将被Spring自动识别并用于配置在Spring AOP.
例如:
@Aspect
public class SecurityHandler {
}
配置文件中定义
<bean id=”securityHandler” class=com.sinoest.spring.SecutityHandler”/>
切面(用@Aspect注解的类)和其他类一样有方法和字段定义。他们可能包括切入点和通知等。
3.3. 声明一个切入点(pointcut)
一个切入点的声明有两个部分:
1、 一个包含名字和任意参数的签名。(可以通过一个普通方法的定义来提供)
2、 一个切入点表达式。(该表达式决定了我们关注哪个方法的执行,并且切入点表达式使用@Pointcut注解来表示)
切入点表达式可以使用’&’,’||’和’!’来合并。
例如:
@Pointcut("execution(* add*(..)) || execution(* del*(..))")//the pointcut expression
private void allAddMethod(){}; //the pointcut signature
3.4. 声明通知
通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者之前和之后运行。
² 前置通知(Before advice)
一个切面里使用@Before注解声明前置通知。
@Before("allAddMethod()")
private void checkSecurity() {
System.out.println("----------checkSecurity()---------------");
}
常见表达式的例子。
任意公共方法的执行:
execution(public **(..)):
任何一个以”set”开始的方法的执行:
Execution(* set*(..));
AccountService接口的任意方法的执行:
Execution(* com.xyz.service.AccountService.*(..))
定义在service包里的任意方法的执行:
Execution(* com.xyz.service.*.*(..));
定义在service包里的任意方法的执行:
Execution(* com.xyz.service.*.*(..)):
² 返回后通知(Before advice)
返回后通知通常在一个匹配的方法返回的时候执行。
Import org.aspectj.lang.annotaion.Aspect;
Import org.aspectj.lang.annotation.AfterReturnning;
@Aspect
Public class AfterReturnningExample{
@AfterReturnning(“com.xyz.myapp.SystemArchitecture.dataAccessOperation”)
Public void doAccessCheck(){
}
}
² 抛出后通知(After throwing advice)
抛出后通知在一个方法抛出异常后执行。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
Public class AfterThrowingExample {
@AfterThrowing(“com.xyz.myapp.SystemArchitecture.dataAccessOperation()”)
public void doRecoveryActions(){
}
}
² 后通知(After (finally) advice)
不论一个方法如何结束,在它结束后都会运行。
Import org.aspectj.lang.annotation.Aspect;
Import org.aspectj.lang.annotation.After;
@Aspect
Public class AfterFinnallyExample{
@After(“com.xyz.myapp.SystemArchitecture.dataAccessOperation()”)
Public void doReleaseLock(){
}
}
² 环绕通知(Around Advice)
环绕通知在一个方法执行之前和之后执行。它使得通知有机会既在一个方法执行之前又在执行之后运行。并且,它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。请尽量使用简单的满足你需求的通知。(比如前置通知(beforeadvice)也可适用的情况下不要适用环绕通知)
环绕通知适用@Around注解来声明。通知的第一个参数必须是ProceedingJoinPoint类型。在通知体内调用ProceedingJoinPoint的proceed()方法将会导致潜在的连接点方法执行。
Import org.aspect.lang.annotation.Aspect;
Import org.aspectj.lang.annotation.Around;
Import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
Public class AroundExample{
@Around(“com.xyz.myapp.SystemArchitecture.businessService()”);
Public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable{
//start stopwatch
Object retval=pjp.proceed()
//stop stopwatch
Return retVal;
}
}
4. Schema-based AOP support支持
Spring 2.0也提供了使用新的”aop”命名空间来定义一个切面。
4.1. 声明一个切面
<beans>
<bean id="securityHandler" class="com.sinoest.spring.SecurityHandler"/>
<bean id="userManager" class="com.sinoest.spring.UserManagerImpl"/>
<aop:config><!—切面跟元素->
<aop:aspect id="security" ref="securityHandler">
..
</aop:aspect>
</aop:config>
</beans>
4.2. 声明一个切入点
<beans>
<bean id="securityHandler" class="com.sinoest.spring.SecurityHandler"/>
<bean id="userManager" class="com.sinoest.spring.UserManagerImpl"/>
<aop:config>
<aop:aspect id="security" ref="securityHandler">
<!—声明一切入点—>
<aop:pointcut id="allAddMethod"expression="execution(* add*(..))"/>
<aop:before method="checkSecurity"pointcut-ref="allAddMethod"/>
</aop:aspect>
</aop:config>
</beans>
4.2. 声明通知
<beans>
<bean id="securityHandler" class="com.sinoest.spring.SecurityHandler"/>
<bean id="userManager" class="com.sinoest.spring.UserManagerImpl"/>
<aop:config>
<aop:aspect id="security" ref="securityHandler">
<!—声明一切入点—>
<aop:pointcut id="allAddMethod"expression="execution(* com.sinoest.spring.UserManagerImpl.add*(..))"/>
<!—声明通知à
<aop:before method="checkSecurity" pointcut-ref="allAddMethod"/>
</aop:aspect>
</aop:config>
</beans>
5.代理模式选择
² Spring AOP还是完全使用AspectJ?
做能起作用的最简单的事。Spring AOP比完全使用AspectJ更加简单,因为它不需要引入AspectJ的编译器到你的开发和构建过程中。
² Spring AOP中使用@AspectJ还是XML?
如果你选择使用Spring AOP,那么你可以选择@AspectJ或XML风格。总的来说,如果你使用Java5,我们建议使用@AspectJ风格。显然如果你不是运行在Java5上,xml风格是最佳的选择。
XML风格对现有的Spring用户来说更加习惯,当使用AOP作为工具来配置企业服务时,XML会是一个很好的选择。对于XML风格,从你的配置中可以清晰的表明在系统中存在哪些切面。
XML风格有两个缺点:
1、它不能完全将需求实现的地方封装到一个位置。
2、XML风格同@AspectJ风格所能表达的内容相比有更多的限制。仅仅支持”sigleton”切面实例模型,并且不能在XML中组合命名连接点的声明。
6.Spring代理机制
Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议尽量使用JDK的动态代理)
如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。若目标对象没有实现任何接口,则创建一个CGLIB代理。
7. 练习和作业
7.1. 练习
练习课上的例子.
7.2. 作业
总结所学内容
_______________________________________________________________________________________________
IOC
spring ioc原理(浅显易懂)
IoC与DI
首先想说说IoC(Inversionof Control,控制倒转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。
那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。如果你还不明白的话,我决定放弃。
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(DependencyInjection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢?Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。关于反射的相关资料请查阅javadoc。
理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。
如果还不明白,放弃java吧!
下面来让大家了解一下Spring到底是怎么运行的。
Java代码
publicstatic void main(String[] args) {
ApplicationContext context = newFileSystemXmlApplicationContext(
"applicationContext.xml");
Animal animal = (Animal)context.getBean("animal");
animal.say();
}
这段代码你一定很熟悉吧,不过还是让我们分析一下它吧,首先是applicationContext.xml
Java代码
<beanid="animal" class="phz.springframework.test.Cat">
<property name="name"value="kitty" />
</bean>
他有一个类phz.springframework.test.Cat
Java代码
publicclass Cat implements Animal {
private String name;
public void say() {
System.out.println("I am " + name +"!");
}
public void setName(String name) {
this.name = name;
}
}
实现了phz.springframework.test.Animal接口
Java代码
publicinterface Animal {
public void say();
}
很明显上面的代码输出I amkitty!
那么到底Spring是如何做到的呢?
接下来就让我们自己写个Spring来看看Spring到底是怎么运行的吧!
首先,我们定义一个Bean类,这个类用来存放一个Bean拥有的属性
Java代码
/*Bean Id */
private String id;
/* Bean Class */
private String type;
/* Bean Property */
private Map<String, Object> properties =new HashMap<String, Object>();
一个Bean包括id,type,和Properties。
接下来Spring就开始加载我们的配置文件了,将我们配置的信息保存在一个HashMap中,HashMap的key就是Bean的 Id ,HasMap的value是这个Bean,只有这样我们才能通过context.getBean("animal")这个方法获得Animal这个类。我们都知道Spirng可以注入基本类型,而且可以注入像List,Map这样的类型,接下来就让我们以Map为例看看Spring是怎么保存的吧
Map配置可以像下面的
Java代码
<beanid="test" class="Test">
<property name="testMap">
<map>
<entry key="a">
<value>1</value>
</entry>
<entry key="b">
<value>2</value>
</entry>
</map>
</property>
</bean>
Spring是怎样保存上面的配置呢?,代码如下:
Java代码
if(beanProperty.element("map") != null) {
Map<String, Object> propertiesMap =new HashMap<String, Object>();
Element propertiesListMap = (Element)beanProperty
.elements().get(0);
Iterator<?> propertiesIterator =propertiesListMap
.elements().iterator();
while (propertiesIterator.hasNext()) {
Element vet = (Element)propertiesIterator.next();
if (vet.getName().equals("entry")){
String key =vet.attributeValue("key");
Iterator<?> valuesIterator =vet.elements()
.iterator();
while (valuesIterator.hasNext()) {
Element value = (Element)valuesIterator.next();
if(value.getName().equals("value")) {
propertiesMap.put(key,value.getText());
}
if(value.getName().equals("ref")) {
propertiesMap.put(key, new String[] {value
.attributeValue("bean")});
}
}
}
}
bean.getProperties().put(name,propertiesMap);
}
接下来就进入最核心部分了,让我们看看Spring到底是怎么依赖注入的吧,其实依赖注入的思想也很简单,它是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。让我们看看具体它是怎么做的吧。
首先实例化一个类,像这样
Java代码
publicstatic Object newInstance(String className) {
Class<?> cls = null;
Object obj = null;
try {
cls = Class.forName(className);
obj = cls.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return obj;
}
接着它将这个类的依赖注入进去,像这样
Java代码
publicstatic void setProperty(Object obj, String name, String value) {
Class<? extends Object> clazz =obj.getClass();
try {
String methodName =returnSetMthodName(name);
Method[] ms = clazz.getMethods();
for (Method m : ms) {
if (m.getName().equals(methodName)) {
if (m.getParameterTypes().length == 1) {
Class<?> clazzParameterType =m.getParameterTypes()[0];
setFieldValue(clazzParameterType.getName(), value, m,
obj);
break;
}
}
}
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
最后它将这个类的实例返回给我们,我们就可以用了。我们还是以Map为例看看它是怎么做的,我写的代码里面是创建一个HashMap并把该HashMap注入到需要注入的类中,像这样,
Java代码
if(value instanceof Map) {
Iterator<?> entryIterator =((Map<?, ?>) value).entrySet()
.iterator();
Map<String, Object> map = newHashMap<String, Object>();
while (entryIterator.hasNext()) {
Entry<?, ?> entryMap = (Entry<?,?>) entryIterator.next();
if (entryMap.getValue() instanceofString[]) {
map.put((String) entryMap.getKey(),
getBean(((String[])entryMap.getValue())[0]));
}
}
BeanProcesser.setProperty(obj, property,map);
}
好了,这样我们就可以用Spring 给我们创建的类了,是不是也不是很难啊?当然Spring能做到的远不止这些,这个示例程序仅仅提供了Spring最核心的依赖注入功能中的一部分。
__________________________________________________________________________________________________________________________