@AspectJ相关文章html
《spring AOP 之二:@AspectJ注解的3种配置》java
《spring AOP 之三:使用@AspectJ定义切入点》spring
《spring AOP 之四:@AspectJ切入点标识符语法详解》express
与 AspectJ 相同的是,Spring AOP 一样须要对目标类进行加强,也就是生成新的 AOP 代理类;与 AspectJ 不一样的是,Spring AOP 无需使用任何特殊命令对 Java 源代码进行编译,它采用运行时动态地、在内存中临时生成“代理类”的方式来生成 AOP 代理。编程
Spring 容许使用 AspectJ Annotation 用于定义方面(Aspect)、切入点(Pointcut)和加强处理(Advice),Spring 框架则可识别并根据这些 Annotation 来生成 AOP 代理。Spring 只是使用了和 AspectJ 5 同样的注解,但并无使用 AspectJ 的编译器或者织入器(Weaver),底层依然使用的是 Spring AOP,依然是在运行时动态生成 AOP 代理,并不依赖于 AspectJ 的编译器或者织入器。安全
简单地说,Spring 依然采用运行时生成动态代理的方式来加强目标对象,因此它不须要增长额外的编译,也不须要 AspectJ 的织入器支持;而 AspectJ 在采用编译时加强,因此 AspectJ 须要使用本身的编译器来编译 Java 文件,还须要织入器。app
为了启用 Spring 对 @AspectJ 方面配置的支持,并保证 Spring 容器中的目标 Bean 被一个或多个方面自动加强,必须在 Spring 配置文件中配置以下片断,框架
AOP的做用这里就再也不做说明了,下面开始讲解一个很简单的入门级例子。
引用一个猴子偷桃,守护者守护果园抓住猴子的小情节。
一、猴子偷桃类(普通类): 函数
二、守护者类(声明为Aspect): 源码分析
三、XML配置文件:
四、测试类:
五、控制台输出:
解说:
1写了一个猴子正在偷桃的方法。
2写了一个标志为@Aspect的类,它是守护者。它会在猴子偷桃以前发现猴子,并在猴子偷桃以后抓住猴子。
原理:
A、@Aspect的声明表示这是一个切面类。
B、@Pointcut使用这个方法能够将com.samter.common.Monkey.stealPeaches(..)方法声明为poincut即切入点。做用,在stealPeaches方法被调用的时候执行2的foundMonkey方法。其中execution是匹配方法执行的切入点,也就是spring最经常使用的切入点定义方式。
C、@Before(value="foundMonkey()"):@Before声明为在切入点方法执行以前执行,然后面没有直接声明切入点,而是value="foundMonkey()",是由于若是@afterReturning等都有所改动的时候都必须所有改动,因此统一用Pointcut的foundMonkey代替,这样子有改动的时候仅需改动一个地方。其余@AfterReturning类同。
3是xml配置文件,里面有具体的注释。
特别说明:Guardian类里面的@Pointcut("execution(* com.samter.common.Monkey.stealPeaches(..))"),若是stealPeaches有参数则..表示全部参数,@AfterReturning("foundMonkey() && args(name,..)")的&& args(name,..)能够获取切入点方法stealPeaches的参数。
总结:这里列举了一个简单的例子,可是不难引伸到应用中,当你写一个登录系统的时候,你或许要记录谁成功登录了系统,谁登录系统密码错误等等的信息,这样子你用切面是再合适不过的了,总之当你的事务逻辑都设计到日志、安全检查、事务管理等等共同的内容的时候,用切面是要比你没有一个事务逻辑类都有相关代码或者相关引用好得多。
一、经过 Spring 的 XML Schema 配置方式:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" <!--schema方式配置--> xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <!-- 启动 @AspectJ 支持 --> <aop:aspectj-autoproxy /> <bean id="horseman" class="com.dxz.aop.demo4.Horseman" /> <bean id="swordman" class="com.dxz.aop.demo4.Swordman" /> <bean class="com.dxz.aop.demo4.StorageAdvisor" /> </beans>
固然,若是咱们但愿彻底启动 Spring 的“零配置”功能,则还须要启用 Spring 的“零配置”支持,让 Spring 自动搜索指定路径下 Bean 类。
所谓自动加强,指的是 Spring 会判断一个或多个方面是否须要对指定 Bean 进行加强,并据此自动生成相应的代理,从而使得加强处理在合适的时候被调用。
若是不打算使用 Spring 的 XML Schema 配置方式,则应该在 Spring 配置文件中增长以下片断来启用 @AspectJ 支持。
二、经过<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>启动@AspectJ(此时schema中不须要相关的AOP配置)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 启动@AspectJ支持 --> <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/> <!-- 启动@AspectJ支持 效果同上,若两个同时添加即会执行两次before方法--> <!-- <aop:aspectj-autoproxy/> --> <bean id="horseman" class="com.dxz.aop.demo4.Horseman" /> <bean id="swordman" class="com.dxz.aop.demo4.Swordman" /> <bean class="com.dxz.aop.demo4.StorageAdvisor" /> </beans>
上面配置文件中的 AnnotationAwareAspectJAutoProxyCreator 是一个 Bean 后处理器(BeanPostProcessor),该 Bean 后处理器将会为容器中 Bean 生成 AOP 代理,当启动了 @AspectJ 支持后,只要咱们在 Spring 容器中配置一个带 @Aspect 注释的 Bean,Spring 将会自动识别该 Bean,并将该 Bean 做为方面 Bean 处理。
在 Spring 容器中配置方面 Bean(即带 @Aspect 注释的 Bean),与配置普通 Bean 没有任何区别,同样使用 <bean.../> 元素进行配置,同样支持使用依赖注入来配置属性值;若是咱们启动了 Spring 的“零配置”特性,同样可让 Spring 自动搜索,并装载指定路径下的方面 Bean。
不用xml配置文件的状况下,经过@Configuration来装配Spring bean,@EnableAspectJAutoProxy来启动spring AOP功能。见《Spring 3.1新特性之二:@Enable*注解的源码,spring源码分析之定时任务Scheduled注解》
使用 @Aspect 标注一个 Java 类,该 Java 类将会做为方面 Bean,以下面代码片断所示:
package com.dxz.aop.demo6; import org.aspectj.lang.annotation.Aspect; // 使用 @Aspect 定义一个方面类 @Aspect public class LogAspect { // 定义该类的其余内容 //... }
方面类(用 @Aspect 修饰的类)和其余类同样能够有方法、属性定义,还可能包括切入点、加强处理定义。
当咱们使用 @Aspect 来修饰一个 Java 类以后,Spring 将不会把该 Bean 当成组件 Bean 处理,所以负责自动加强的后处理 Bean 将会略过该 Bean,不会对该 Bean 进行任何加强处理。
开发时无须担忧使用 @Aspect 定义的方面类被加强处理,当 Spring 容器检测到某个 Bean 类使用了 @Aspect 标注以后,Spring 容器不会对该 Bean 类进行加强。
下面将会考虑采用 Spring AOP 来改写前面介绍的例子:
下面例子使用一个简单的 Chinese 类来模拟业务逻辑组件:
Chinese.java
package com.dxz.aop.demo6; import org.springframework.stereotype.Component; @Component public class Chinese { // 实现 Person 接口的 sayHello() 方法 public String sayHello(String name) { String ret = name + " Hello , Spring AOP"; System.out.println(ret); return ret; } // 定义一个 eat() 方法 public void eat(String food) { System.out.println("我正在吃 :" + food); } }
提供了上面 Chinese 类以后,接下来假设一样须要为上面 Chinese 类的每一个方法增长事务控制、日志记录,此时能够考虑使用 Around、AfterReturning 两种加强处理。
先看 AfterReturning 加强处理代码。
AfterReturningAdviceTest.java
package com.dxz.aop.demo6; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; // 定义一个方面 @Aspect public class AfterReturningAdviceTest { // 匹配 com.dxz.aop.demo6 包下全部类的下的全部方法的执行做为切入点 @AfterReturning(returning = "rvt", pointcut = "execution(* com.dxz.aop.demo6.*.*(..))") public void log(Object rvt) { System.out.println("AfterReturningAdviceTest==获取目标方法返回值 :" + rvt); } }
上面 Aspect 类使用了 @Aspect 修饰,这样 Spring 会将它当成一个方面 Bean 进行处理。其中程序中粗体字代码指定将会在调用 org.crazyit.app.service.impl 包下的全部类的全部方法以后织入 log(Object rvt) 方法。
再看 Around 加强处理代码:
package com.dxz.aop.demo6; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; // 定义一个方面 @Aspect public class AroundAdviceTest { // 匹配 com.dxz.aop.demo6 包下全部类的下的全部方法的执行做为切入点 @Around("execution(* com.dxz.aop.demo6.*.*(..))") public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable { System.out.println("AroundAdviceTest==执行目标方法以前,模拟开始事务 ..."); // 执行目标方法,并保存目标方法执行后的返回值 Object rvt = jp.proceed(new String[] { "被改变的参数" }); System.out.println("AroundAdviceTest==执行目标方法以后,模拟结束事务 ..."); return rvt + " 新增的内容"; } }
与前面的 AfterReturning 加强处理相似的,此处一样使用了 @Aspect 来修饰前面 Bean,其中粗体字代码指定在调用com.dxz.aop.demo6 包下的全部类的全部方法的“先后(Around)” 织入 processTx(ProceedingJoinPoint jp) 方法须要指出的是,虽然此处只介绍了 Spring AOP 的 AfterReturning、Around 两种加强处理,但实际上 Spring 还支持 Before、After、AfterThrowing 等加强处理,关于 Spring AOP 编程更多、更细致的编程细节,能够参考《轻量级 Java EE 企业应用实战》一书。
本示例采用了 Spring 的零配置来开启 Spring AOP,所以上面 Chinese 类使用了 @Component 修饰,而方面 Bean 则使用了 @Aspect 修饰,方面 Bean 中的 Advice 则分别使用了 @AfterReturning、@Around 修饰。接下来只要为 Spring 提供以下配置文件便可:
applicationContext-aop6.xml
<?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 指定自动搜索 Bean 组件、自动搜索方面类 --> <context:component-scan base-package="com.dxz.aop.demo6"> <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect" /> </context:component-scan> <!-- 启动 @AspectJ 支持 --> <aop:aspectj-autoproxy /> </beans>
接下来按传统方式来获取 Spring 容器中 chinese Bean、并调用该 Bean 的两个方法,程序代码以下:
BeanTest.java
package com.dxz.aop.demo6; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BeanTest { public static void main(String[] args) { // 建立 Spring 容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-aop6.xml"); Chinese p = ctx.getBean("chinese", Chinese.class); System.out.println(p.sayHello("张三")); p.eat("西瓜"); } }
从上面开发过程能够看出,对于 Spring AOP 而言,开发者提供的业务组件、方面 Bean 并无任何特别的地方。只是方面 Bean 须要使用 @Aspect 修饰便可。程序不须要使用特别的编译器、织入器进行处理。
运行上面程序,将能够看到以下执行结果:
虽然程序是在调用 Chinese 对象的 sayHello、eat 两个方法,但从上面运行结果不难看出:实际执行的绝对不是 Chinese 对象的方法,而是 AOP 代理的方法。也就是说,Spring AOP 一样为 Chinese 类生成了 AOP 代理类。这一点可经过在程序中增长以下代码看出:
System.out.println(p.getClass());
上面代码能够输出 p 变量所引用对象的实现类,再次执行程序将能够看到上面代码产生class com.dxz.aop.demo6.Chinese$$EnhancerBySpringCGLIB$$7d0b6d20的输出,这才是 p 变量所引用的对象的实现类,这个类也就是 Spring AOP 动态生成的 AOP 代理类。从 AOP 代理类的类名能够看出,AOP 代理类是由 CGLIB 来生成的。
若是将上面程序程序稍做修改:只要让上面业务逻辑类 Chinese 类实现一个任意接口——这种作法更符合 Spring 所倡导的“面向接口编程”的原则。假设程序为 Chinese 类提供以下 Person 接口,并让 Chinese 类实现该接口:
Person.java
package com.dxz.aop.demo6; public interface Person { String sayHello(String name); void eat(String food); }
Chinese修改实现Person接口:
@Component public class Chinese implements Person {
接下来让 BeanTest 类面向 Person 接口、而不是 Chinese 类编程。即将 BeanTest 类改成以下形式:
BeanTest.java
package com.dxz.aop.demo6; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BeanTest { public static void main(String[] args) { // 建立 Spring 容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-aop6.xml"); Person p = ctx.getBean("chinese", Person.class); System.out.println(p.sayHello("张三")); p.eat("西瓜"); System.out.println(p.getClass()); } }
原来的程序是将面向 Chinese 类编程,如今将该程序改成面向 Person 接口编程,再次运行该程序,程序运行结果没有发生改变。只是 System.out.println(p.getClass()); 将会输出 class com.sun.proxy.$Proxy10,这说明此时的 AOP 代理并非由 CGLIB 生成的,而是由 JDK 动态代理生成的。
Spring AOP 框架对 AOP 代理类的处理原则是:若是目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类;若是目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类——不过这个选择过程对开发者彻底透明、开发者也无需关心。
Spring AOP 会动态选择使用 JDK 动态代理、CGLIB 来生成 AOP 代理,若是目标类实现了接口,Spring AOP 则无需 CGLIB 的支持,直接使用 JDK 提供的 Proxy 和 InvocationHandler 来生成 AOP 代理便可。关于如何 Proxy 和 InvocationHandler 来生成动态代理不在本文介绍范围以内,若是读者对 Proxy 和 InvocationHandler 的用法感兴趣则可自行参考 Java API 文档或《疯狂 Java 讲义》。
在spring3.1及以上,spring能够不用xml配置装载bean了,在@Configuration注解的环境里,能够经过@EnableAspectJAutoProxy启动spring AOP功能。微调上面的示例以下:
在@Aspect中
package com.dxz.aop.demo6; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.springframework.context.annotation.Configuration; // 定义一个方面 @Aspect @Configuration public class AfterReturningAdviceTest { // 匹配 com.dxz.aop.demo6 包下全部类的下的全部方法的执行做为切入点 @AfterReturning(returning = "rvt", pointcut = "execution(* com.dxz.aop.demo6.*.*(..))") public void log(Object rvt) { System.out.println("AfterReturningAdviceTest==获取目标方法返回值 :" + rvt); } }
增长spring配置类
package com.dxz.aop.demo6; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; @Configuration @ComponentScan @EnableAspectJAutoProxy @Import({AfterReturningAdviceTest.class})/*@Aspect能够生效,至关于Configuration类做用,都是配置类*/ public class AppConfig { @Bean(name = "chinese") public Chinese chinese() { return new Chinese(); } }
启动类:
package com.dxz.aop.demo6; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test6 { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Person outPut = (Person) context.getBean("chinese"); outPut.sayHello("duan"); } }
结果:
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@c4437c4: startup date [Tue Dec 26 17:03:32 CST 2017]; root of context hierarchy AfterReturningAdviceTest==获取目标方法返回值 :com.dxz.aop.demo6.Chinese@72967906 duan Hello , Spring AOP AfterReturningAdviceTest==获取目标方法返回值 :duan Hello , Spring AOP
3、注解
2.1 @Aspect
做用是把当前类标识为一个切面供容器读取
2.2 @Before
标识一个前置加强方法,至关于BeforeAdvice的功能,类似功能的还有
2.3 @AfterReturning
后置加强,至关于AfterReturningAdvice,方法正常退出时执行
2.4 @AfterThrowing
异常抛出加强,至关于ThrowsAdvice
2.5 @After
final加强,不论是抛出异常或者正常退出都会执行
2.6 @Around
环绕加强,至关于MethodInterceptor
2.7 @DeclareParents
引介加强,至关于IntroductionInterceptor
execution函数用于匹配方法执行的链接点,语法为:
execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))
参数部分容许使用通配符:
* 匹配任意字符,但只能匹配一个元素
.. 匹配任意字符,能够匹配任意多个元素,表示类时,必须和*联合使用
+ 必须跟在类名后面,如Horseman+,表示类自己和继承或扩展指定类的全部类
示例中的* chop(..)解读为:
方法修饰符 无
返回类型 *匹配任意数量字符,表示返回类型不限
方法名 chop表示匹配名称为chop的方法
参数 (..)表示匹配任意数量和类型的输入参数
异常模式 不限
更多示例:
void chop(String,int)
匹配目标类任意修饰符方法、返回void、方法名chop、带有一个String和一个int型参数的方法
public void chop(*)
匹配目标类public修饰、返回void、方法名chop、带有一个任意类型参数的方法
public String *o*(..)
匹配目标类public修饰、返回String类型、方法名中带有一个o字符、带有任意数量任意类型参数的方法
public void *o*(String,..)
匹配目标类public修饰、返回void、方法名中带有一个o字符、带有任意数量任意类型参数,但第一个参数必须有且为String型的方法
也能够指定类:
public void examples.chap03.Horseman.*(..)
匹配Horseman的public修饰、返回void、不限方法名、带有任意数量任意类型参数的方法
public void examples.chap03.*man.*(..)
匹配以man结尾的类中public修饰、返回void、不限方法名、带有任意数量任意类型参数的方法
指定包:
public void examples.chap03.*.chop(..)
匹配examples.chap03包下全部类中public修饰、返回void、方法名chop、带有任意数量任意类型参数的方法
public void examples..*.chop(..)
匹配examples.包下和全部子包中的类中public修饰、返回void、方法名chop、带有任意数量任意类型参数的方法
能够用这些表达式替换StorageAdvisor中的代码并观察效果
除了execution(),Spring中还支持其余多个函数,这里列出名称和简单介绍,以方便根据须要进行更详细的查询
4.1 @annotation()
表示标注了指定注解的目标类方法
例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示标注了@Transactional的方法
4.2 args()
经过目标类方法的参数类型指定切点
例如 args(String) 表示有且仅有一个String型参数的方法
4.3 @args()
经过目标类参数的对象类型是否标注了指定注解指定切点
如 @args(org.springframework.stereotype.Service) 表示有且仅有一个标注了@Service的类参数的方法
4.4 within()
经过类名指定切点
如 with(examples.chap03.Horseman) 表示Horseman的全部方法
4.5 target()
经过类名指定,同时包含全部子类
如 target(examples.chap03.Horseman) 且Elephantman extends Horseman,则两个类的全部方法都匹配
4.6 @within()
匹配标注了指定注解的类及其全部子类
如 @within(org.springframework.stereotype.Service) 给Horseman加上@Service标注,则Horseman和Elephantman 的全部方法都匹配
4.7 @target()
全部标注了指定注解的类
如 @target(org.springframework.stereotype.Service) 表示全部标注了@Service的类的全部方法
4.8 this()
大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配
表达式可由多个切点函数经过逻辑运算组成
5.1 &&
与操做,求交集,也能够写成and
例如 execution(* chop(..)) && target(Horseman) 表示Horseman及其子类的chop方法
5.2 ||
或操做,求并集,也能够写成or
例如 execution(* chop(..)) || args(String) 表示名称为chop的方法或者有一个String型参数的方法
5.3 !
非操做,求反集,也能够写成not
例如 execution(* chop(..)) and !args(String) 表示名称为chop的方法可是不能是只有一个String型参数的方法