论坛首页 Java企业应用论坛

Spring3开发实战 之 第三章:AOP开发

浏览 2735 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-08-04   最后修改:2012-08-04

 


AOP是什么(Aspect   Oriented   Programming)
AOP是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。
AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。
 AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。



AOP能干什么,也是AOP带来的好处
1:降低模块的耦合度
2:使系统容易扩展
3:设计决定的迟绑定:使用AOP,设计师可以推迟为将来的需求作决定,因为它
可以把这种需求作为独立的方面很容易的实现。
4:更好的代码复用性
  
仍然存在问题:
大家会发现,需要修改的地方分散在很多个文件中,如果需要修改的文件多那么修改的量会很大,这无疑会增加出错的几率,并且加大系统维护的难度。
而且,如果添加功能的需求是在软件开发的后期才提出的话,这样大量修改已有的文件,也不符合基本的“开-闭原则”。
 
改进的解决方案
采用装饰器模式或者代理模式来实现。
 
装饰器模式定义
动态地给一个对象添加一些额外的职责。就增加功能来说, 装饰器模式相比生成子类更为灵活。
代理模式定义
为其他对象提供一种代理以控制对这个对象的访问
 
JDK动态代理解决方案(比较通用的解决方案)

java代码:
  1. public class MyInvocationHandler implements InvocationHandler {  
  2.     private Object target;  
  3.     public MyInvocationHandler(Object target) {  
  4.         this.target = target;  
  5.     }  
  6.     public Object invoke(Object proxy, Method method, Object[] args)  
  7. throws Throwable {  
  8.         //1.记录日志    2.时间统计开始      3.安全检查  
  9.         Object retVal = method.invoke(target, args);  
  10.         //4.时间统计结束  
  11.         return retVal;    
  12.     }  
  13.     public Object proxy() {  
  14.         return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
  15.                 target.getClass().getInterfaces(), new MyInvocationHandler(target));  
  16.     }  
  17. }  
CGLIB动态代理的解决方案:

java代码:
  1. public class MyInterceptor implements MethodInterceptor  {  
  2.     private Object target;  
  3.     public MyInterceptor(Object target) {  
  4.         this.target = target;  
  5.     }  
  6.     public Object intercept(Object proxy, Method method, Object[] args,  
  7.                             MethodProxy invocation) throws Throwable {  
  8.         //1.记录日志 2.时间统计开始   3.安全检查  
  9.         Object retVal = invocation.invoke(target, args);  
  10.         //4.时间统计结束  
  11.         return retVal;    
  12.     }  
  13.     public Object proxy() {  
  14.         return Enhancer.create(target.getClass(), new MyInterceptor(target));  
  15.     }  
  16. }  
JDK动态代理的特点
不能代理类,只能代理接口
CGLIB动态代理的特点
能代理类和接口,不能代理final类
动态代理的本质
用来实现对目标对象进行增强,最终表现为类,只不过是动态创建子类,不用手工生成子类。
 
动态代理的限制
只能在父类方法被调用之前或之后进行增强(功能的修改),不能在中间进行修改,要想在方法调用中增强,需要ASM(一个Java 字节码操作和分析框架)
更好的解决方案——AOP提供
人们认识到,传统的程序经常表现出一些不能自然地适合跨越多个程序模块的行为,例如日志记录、对上下文敏感的错误处理等等,人们将这种行为称为“横切关注点(CrossCuttingConcern)”,因为它跨越了给定编程模型中的典型职责界限。
如果使用过用于密切关注点的代码,您就会知道缺乏模块性所带来的问题。因为横切行为的实现是分散的,开发人员发现这种行为难以作逻辑思维、实现和更改。因此,面向方面的编程AOP应运而生。
 
回过头来再次理解AOP的概念。
关注点
就是所关注的公共功能,比如像事务管理,就是一个关注点。表示 “要做什么”
连接点(Joinpoint)
在程序执行过程中某个特定的点,通常在这些点需要添加关注点的功能,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是代表一个方法的执行。表示 “在什么地方做”
通知(Advice):
在切面的某个特定的连接点(Joinpoint)上执行的动作。
通知有各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。表示 “具体怎么做”
切面/方面(Aspect)
一个关注点的模块化,这个关注点可能会横切多个对象。 综合表示  在什么地方,要 做什么,以及具体如何做 
切入点(Pointcut):
匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
目标对象(Target Object):
被一个或者多个切面所通知(advise)的对象。也有人把它叫做 被通知(advised) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
AOP代理(AOP Proxy):
 AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是 通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。 注意:Spring引入的基于模式(schema-based)风格和@AspectJ注解风格的切面声明,对于使用这些风格的用户来说,代理的创建是透明的。
 
 
织入(Weaving)
把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象的过程。也就是说织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
引入(Introduction):
也被称为内部类型声明(inter-type declaration)。为已有的类声明额外的方法或者某个类型的字段。 Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。 例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
前置通知(Before advice):
在某连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
返回后通知(After returning advice):
在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
抛出异常后通知(After throwing advice):
在方法抛出异常退出时执行的通知。
后通知(After (finally) advice):
当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
环绕通知(Around Advice):
包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
 
构建环境和前面一样
定义一个接口如下:

java代码:
  1. package cn.javass.Spring3.aop;  
  2. public interface Api {  
  3. public String testAop();  
  4. }  
写一个类实现这个接口

java代码:
  1. public class Impl implements Api{  
  2. public String testAop() {  
  3. System.out.println("test aop");  
  4. return "aop is ok";  
  5. }  
  6. }  
写一个类作为Before的Advice,没有任何特殊要求,就是一个普通类

java代码:
  1. public class MyBefore {  
  2. public void b1(){  
  3. System.out.println("now befoer--------->");  
  4. }  
  5. }  
配置文件,要注意配置的时候要保证一定要有命名空间aop的定义,如下:

java代码:
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4. xmlns:context="http://www.springframework.org/schema/context"  
  5. xmlns:aop="http://www.springframework.org/schema/aop"  
  6. xmlns:tx="http://www.springframework.org/schema/tx"  
  7. xsi:schemaLocation="  
   发表时间:2012-08-04  

视频配套PPT,视频地址【 Spring3开发实战-独家视频课程 】 
原创内容 转自请注明【 http://sishuok.com/forum/blogPost/list/0/2542.html#7315】 

启用@AspectJ支持
通过在你的Spring的配置中引入下列元素来启用Spring对@AspectJ的支持:
<aop:aspectj-autoproxy/>
声明一个方面
在application context中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并用于配置在Spring AOP。
配置如:

java代码:
  1. <bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">  
  2.    <!-- configure properties of aspect here as normal -->  
  3. </bean>  
  4. Java类:  
  5. @Aspect  
  6. public class NotVeryUsefulAspect {  
  7. }  
声明一个切入点(pointcut
使用 @Pointcut 注解来表示 ,示例如下:

java代码:
  1. @Pointcut("execution(* transfer(..))")  
  2. private void anyOldTransfer() {}  
切入点指定者的支持
Spring AOP 支持在切入点表达式中使用如下的AspectJ切入点指定者:
1:execution:匹配方法执行的连接点,这是你将会用到的Spring的最主要的切入点指定者。
2:within:限定匹配特定类型的连接点(在使用Spring AOP的时候,在匹配的类型中定义的方法的执行)。
3:this:限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。
4: target:限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中目标对象(被代理的appolication object)是指定类型的实例。
5: args:限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。
6: @target:限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中执行的对象的类已经有指定类型的注解。
7: @args:限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中实际传入参数的运行时类型有指定类型的注解。
8: @within:限定匹配特定的连接点,其中连接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。
9: @annotation:限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中连接点的主题有某种给定的注解
合并切入点表达式
切入点表达式可以使用‘&&', '||' 和 '!'来合并.还可以通过名字来指向切入点表达式。
切入点表达式的基本语法
Spring AOP 用户可能会经常使用 execution pointcut designator。执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
除了返回类型模式(上面代码片断中的ret-type-pattern),名字模式和参数模式以外,所有的部分都是可选的。 返回类型模式决定了方法的返回类型必须依次匹配一个连接点。
类型匹配模式
1:*:匹配任何数量字符;比如模式 (*,String) 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型
2:..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数,可以使零到多个。
3: +:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
类型匹配模式示例
1:java.lang.String    匹配String类型;
2:java.*.String       匹配java包下的任何“一级子包”下的String类型;
                       如匹配java.lang.String,但不匹配java.lang.ss.String
3:java..*             匹配java包及任何子包下的任何类型;
                       如匹配java.lang.String、java.lang.annotation.Annotation
4:java.lang.*ing      匹配任何java.lang包下的以ing结尾的类型;
5:java.lang.Number+   匹配java.lang包下的任何Number的子类型;
                       如匹配java.lang.Integer,也匹配java.math.BigInteger
切入点表达式的基本示例,使用execution
1:public * *(..) 
任何公共方法的执行
2:* cn.javass..IPointcutService.*()  
cn.javass包及所有子包下IPointcutService接口中的任何无参方法
3:* cn.javass..*.*(..) 
cn.javass包及所有子包下任何类的任何方法
4:* cn.javass..IPointcutService.*(*)
cn.javass包及所有子包下IPointcutService接口的任何只有一个参数方法
5:* (!cn.javass..IPointcutService+).*(..)
非“cn.javass包及所有子包下IPointcutService接口及子类型”的任何方法
6:* cn.javass..IPointcutService+.*()
cn.javass包及所有子包下IPointcutService接口及子类型的的任何无参方法
7:* cn.javass..IPointcut*.test*(java.util.Date)
cn.javass包及所有子包下IPointcut前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法签名的参数类型进行匹配的,而不是根据执行时传入的参数类型决定的如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的。
8:* cn.javass..IPointcut*.test*(..)  throws IllegalArgumentException, ArrayIndexOutOfBoundsException
cn.javass包及所有子包下IPointcut前缀类型的的任何方法,且抛出IllegalArgumentException和ArrayIndexOutOfBoundsException异常
9:* (cn.javass..IPointcutService+ && java.io.Serializable+).*(..)
任何实现了cn.javass包及所有子包下IPointcutService接口和java.io.Serializable接口的类型的任何方法
10:@java.lang.Deprecated * *(..)
任何持有@java.lang.Deprecated注解的方法
11:@java.lang.Deprecated @cn.javass..Secure  * *(..)
任何持有@java.lang.Deprecated和@cn.javass..Secure注解的方法
12:@(java.lang.Deprecated || cn.javass..Secure) * *(..)
任何持有@java.lang.Deprecated或@ cn.javass..Secure注解的方法
13:(@cn.javass..Secure  *)  *(..)
任何返回值类型持有@cn.javass..Secure的方法
14:*  (@cn.javass..Secure *).*(..)
任何定义方法的类型持有@cn.javass..Secure的方法
15:* *(@cn.javass..Secure (*) , @cn.javass..Secure (*))
任何签名带有两个参数的方法,且这个两个参数都被@ Secure标记了,如public void test(@Secure String str1, @Secure String str1);
16:* *((@ cn.javass..Secure *))或* *(@ cn.javass..Secure *)
任何带有一个参数的方法,且该参数类型持有@ cn.javass..Secure;如public void test(Model model);且Model类上持有@Secure注解
17:* *(@cn.javass..Secure (@cn.javass..Secure *) ,@ cn.javass..Secure (@cn.javass..Secure *))
任何带有两个参数的方法,且这两个参数都被@ cn.javass..Secure标记了;且这两个参数的类型上都持有@ cn.javass..Secure;
18:* *(java.util.Map<cn.javass..Model, cn.javass..Model>, ..)
任何带有一个java.util.Map参数的方法,且该参数类型是以<cn.javass..Model, cn.javass..Model>为泛型参数;注意只匹配第一个参数为java.util.Map,不包括子类型;如public void test(HashMap<Model, Model> map, String str);将不匹配,必须使用“* *(java.util.HashMap<cn.javass..Model,cn.javass..Model>, ..)”进行匹配;而public void test(Map map, int i);也将不匹配,因为泛型参数不匹配
19:* *(java.util.Collection<@cn.javass..Secure *>)
任何带有一个参数(类型为java.util.Collection)的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解;如public void test(Collection<Model> collection);Model类型上持有@cn.javass..Secure
切入点表达式的基本示例,使用within匹配指定类型内的方法
1:within(cn.javass..*)
cn.javass包及子包下的任何方法执行
2:within(cn.javass..IPointcutService+)
cn.javass包或所有子包下IPointcutService类型及子类型的任何方法
3:within(@cn.javass..Secure *)
持有cn.javass..Secure注解的任何类型的任何方法必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
 
切入点表达式的基本示例,使用this
使用“this(类型全限定名)”匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;注意this中使用的表达式必须是类型全限定名,不支持通配符
1:this(cn.javass.spring.chapter6.service.IPointcutService)
当前AOP对象实现了 IPointcutService接口的任何方法
2:this(cn.javass.spring.chapter6.service.IIntroductionService)
当前AOP对象实现了 IIntroductionService接口的任何方法也可能是引入接口
切入点表达式的基本示例,使用target
使用“target(类型全限定名)”匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符
1:target(cn.javass.spring.chapter6.service.IPointcutService)
当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法
2:target(cn.javass.spring.chapter6.service.IIntroductionService)
当前目标对象(非AOP对象) 实现了IIntroductionService 接口的任何方法不可能是引入接口
切入点表达式的基本示例,使用args
使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法;注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用
1:args (java.io.Serializable,..)
任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的
切入点表达式的基本示例,使用@within
使用“@within(注解类型)”匹配所以持有指定注解类型内的方法;注解类型也必须是全限定类型名
1:@within cn.javass.spring.chapter6.Secure)
任何目标对象对应的类型持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
切入点表达式的基本示例,使用@target
使用“@target(注解类型)”匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;注解类型也必须是全限定类型名
1:@target (cn.javass.spring.chapter6.Secure)
任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
切入点表达式的基本示例,使用@args
使用“@args(注解列表)”匹配当前执行的方法传入的参数持有指定注解的执行;注解类型也必须是全限定类型名
1:@args (cn.javass.spring.chapter6.Secure)
任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解 cn.javass.spring.chapter6.Secure;动态切入点,类似于arg指示符;
切入点表达式的基本示例,使用@annotation
使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;注解类型也必须是全限定类型名
1:@annotation(cn.javass.spring.chapter6.Secure )
当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配
切入点表达式的基本示例,使用bean
使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring AOP扩展的,在AspectJ中无相应概念
1:bean(*Service)
匹配所有以Service命名(id或name)结尾的Bean
切入点表达式的基本示例,使用reference pointcut
引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持,如下所示:

java代码:
  1. @Pointcut(value="bean(*Service)"//命名切入点1  
  2. private void pointcut1(){}  
  3. @Pointcut(value="@args(cn.javass.spring.chapter6.Secure)"//命名切入点2  
  4. private void pointcut2(){}  
  5.    
  6. @Before(value = "pointcut1() && pointcut2()")     //引用命名切入点  
  7. public void referencePointcutTest1(JoinPoint jp) {  
  8.     dump("pointcut1() && pointcut2()", jp);  
  9. }  
声明通知
通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者之前和之后运行。 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。
前置通知(Before advice) ,使用 @Before 注解声明

java代码:
  1. @Aspect  
  2. public class BeforeExample {  
  3.   @Before("execution(* com.xyz.myapp.dao.*.*(..))")  
  4.   public void doAccessCheck() {  
  5. // ...  
  6.   }  
  7. }  
返回后通知(After returning advice) ,使用 @AfterReturning注解声明
后通知(After (finally) advice) ,使用 @After注解声明
抛出后通知(After throwing advice) ,使用 @AfterThrowing注解声明
可以将抛出的异常绑定到通知的一个参数上 ,如下:

java代码:
  1. @AfterThrowing(  
  2. pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",  
  3. throwing="ex")  
  4.   public void doRecoveryActions(Exception ex) {  
  5. // ...  
  6.   }  
环绕通知(Around Advice) ,使用 @Around注解声明

java代码:
  1. @Around("com.xyz.myapp.SystemArchitecture.businessService()")  
  2. public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {  
  3. // start stopwatch  
  4. Object retVal = pjp.proceed();  
  5. // stop stopwatch  
  6. paddi
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics