Spring AOP 之 Aspect
2017-05-02 13:56
483 查看
之前我用了很多篇幅去介绍AOP的,现在我们使用一个最为常用的AOP使用方式,使用基于AspectJ的表达式进行定义切面,我们采用两种方式一种是通过annotation的另外一种就是通过XML进行配置的方式,在AspectJ中是使用annotation的方式进行使用的,所以我们首先会介绍一下如何去使用annotation去完成我们的代理功能。
在我们使用@AspectJ切面的前提是,我们在配置文件中启动aspectJ的自动代理:
其中asp:aspectj-autoproxy标签实际上使用 org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator 进行代理自动创建的,所以如果不想引入aop命名空间 直接 使用 <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" /> 也是等价的。当然我们这里也可以使用 proxy-target-class="true"
这个也不用多说了,就是使用CGlib直接对模板对象生成虚拟子类进行代理。
然后我们创建一个类并使用@Aspect进行定义切点和增强(注意我们在@Before中的参数 execution 是比较常用的切点描述方法,后面会有更加详细的讲解):
然后我们使用正常获取bean的方式进行调用,发现AspectExample.begin方法已经织入到userService中的login方法当中
刚刚我们可以发现我们在@Before里面使用了execution,这个是属于切点表达式函数的一种,而且我们在execution里面使用了如.. *等这些通配符,当然aspectJ的切点表达式函数远远不止这些,还有以下这些都属于aspectJ的切点表达式函数,在本文后面会逐一举例说明(包括有:execution,@annotation,args,@args,within,target,@within,@target,this),目前先介绍表达式的通配符。
@AspectJ支持3种通配符:
* 匹配任意字符,但是只能匹配一个元素,例如一个类、一个参数、一个方法。
.. 匹配任意字符,可以匹配任意多个元素。表示类的时候,必须与*联合使用,但是在表示方法入参的时候可以表示多个参数而不需要联合*一起使用。
+ 表示类以下的所有子类,所以这个必须联合类来使用,而且必须定义在类名的后面,例如:UserService+
刚刚我们使用了@Before 增强,然而Aspect提供了一下的的增强:
@Before : 前置增强,相当于BeforeAdvice,其annotation成员有:
value - > 定义切点
argNames - > 由于无法通过Java反射获得方法的入参名称,所以如果在Java编译未启动调试信息或者需要在运行解析切点,就必须通过这个成员指定所标注的增强方法的参数名。
@AfterReturning : 后置增强,相当于AfterReturningAdvice,其annotation成员如下:
pointcut - >表示切点信息,如果定义了pointcut会覆盖value的属性,和value功能一样
value - > 定义切点
argNames - > 由于无法通过Java反射获得方法的入参名称,所以如果在Java编译未启动调试信息或者需要在运行解析切点,就必须通过这个成员指定所标注的增强方法的参数名。
@Around 环绕增强,相当于MethodInterceptor,Around注解类拥有两个成员
value - > 定义切点
argNames - > 由于无法通过Java反射获得方法的入参名称,所以如果在Java编译未启动调试信息或者需要在运行解析切点,就必须通过这个成员指定所标注的增强方法的参数名。
@After Final增强,这个增强比较提别,没有一个相对应之前用过的增强类,@After 可以看做 ThrowsAdvice和 AfterReturningAdvice的混合,就是说无论正常推出还是抛出异常,这个增强方法都会被执行,一般用于释放资源,相当于tyr{}finally{}的控制流。@Afer注解用于成员如下:
value - > 定义切点
argNames - > 由于无法通过Java反射获得方法的入参名称,所以如果在Java编译未启动调试信息或者需要在运行解析切点,就必须通过这个成员指定所标注的增强方法的参数名。
@DecareParents 引介增强,相当于IntroductionInterceptor, @DecareParents 注解类拥有两个成员:
value - > 定义切点
defaultImpl -> 默认的接口实现
由于DecareParents跟其他的增强使用方式不一样,所以以下会有相应的解释
@DecareParents 引介增强的使用:
同样我们使用UserService类,同时我们新建一个AccountSecurity的接口,然后提供一个resetPassword()的方法,同时我们新建AccountSecurityImpl类,同时通过@DecareParents 让UserService的proxy类implement于AccountSecurity接口,同时使用AccountSecurityImpl作为resetPassword等方法的实现:
以下是AccountSecurity 和 AccountSecurityImpl
创建一个Aspect:
配置文件和上文贴出的代码一直,然后我们尝试调用UserService的代理:
输出如下:
Login : TONY, pwd : PWD
account : TONY , reset password success.
以上通配符和Aspect的增强annotation描述 摘录于《Spring 3.x 企业应用开发实战》。
首先我们先了解如何使用aspectJ的切点表达式函数,下面将会一一介绍:
@annotation() 切面表达式函数
函数中的参数为一个annotation类,定义了该annotation的类方法都会进行织入。下面我们将会自定义一个annotation类名为MyProxy,然后分别在AdminService 和 UserService两个类中标记这个@MyProxy,最后测试效果:
把这个@MyProxy 标记的AdminService.login方法 和 UserService.register方法中:
我也做个一个测试,尝试将@MyProxy直接标记在类中,但是并没有进行织入,说明这个@annotation的表达式函数只对方法有效,有兴趣的也可以测试一下,当然在定义 MyProxy annotation类的时候记得要在@target添加ElementType.Type,这个是annotation的知识我就不多说了,有兴趣可以百度一下annotation的相关知识。
然后我们定义Aspect类(其实我也不知道应该如何称呼这种类,先称它为aspect类吧)
注意我们使用的增强是@AfterReturning 所以是在方法执行之后调用。
现在我们在main方法测试并输出结果:
输出结果中可以看到,只有标记了@MyProxy的类方法才会被织入:
Login : TONY, pwd : PWD
register : TONY , pwd PWD
afterReturning annocation test !
Admin Login : TONY, pwd : PWD
afterReturning annocation test !
Admin register : TONY , pwd PWD
最后我贴出一下本文所有实验所用的不变配置,之后的本文的实验将不会再贴出applicationContext.xml的配置文件:
execution() 切面表达式函数
execution 切面表达式函数是最为常用的表达式函数,语法如下:
execution(<修饰符模式[可选]> <返回类型> <方法名>(<参数>)),以下将列出几个范例:
1、execution(public * *(..)) :定义一个方法修饰符为public的 不限制返回类型 不限制方法名 不限制参数的 方法。
2、execution(* *Service(..) ):定义不限制修饰符(因为例子中忽略没有写) 不限方法的返回类型 方法名以Service结尾 不限参数的 所有方法。
3、execution(* com.tony.service.UserService.*(..)) : 定义com.tony.service.UserService类下的所有方法
4、execution(* com.tony.service.UserService+.*(..)) : 定义com.tony.service.UserService类以及其子类下的所有方法
5、execution(* con.tony.service..*(..)): 定义com.tony.service包下以及其子孙包的所有类的所有方法被织入
6、execution(* com.tony.service.*(..)): 定义com.tony.service包下的所有类的所有方法被织入,但不包括其子孙包的类。
7、execution(* login(String,String)): 定义方法名为login 其参数为两个String类型的方法被织入。
8、execution(* getUserId(String,*)):定义方法名为getUserId 其方法参数有两个一个是String 另外一个为任意类型的 方法被织入。
9、execution(* resetPassword(String,..)):定义方法名为resetPassword 其方法参数有多个第一个参数为String 其余的参数任何类型 的方法被织入。
10、execution(* saveUserInDB(User+)):定义方法名为saveUserInDB 其方法参数是一个User对象获取是User派生的对象。
arg() 切面表达式函数
args接受一个类名,表示目标方法的入参类型为目标类型或者目标类型的派生类型:
以上代码 匹配所有参数只有一个User的方法,如果想匹配方法中第一个参数为User 其他参数不限制可以同样式样aspectJ提供的通配符:
@arg() 切面表达式函数
@arg 接受annotation类,匹配方法中的标记了目标annotation的类,下面是一个范例:
使用MyProxy标记User类
以下方法会匹配:
within() 切面表达式函数
within和execution差不多,但是within最小范围匹配到类,而execution是匹配到方法,所以说execution的功能基本涵盖within。以下是一些例子:
1、within(com.tony.service.*)匹配com.tony.service包一下的所有类
2、within(com.tony.service.UserService) 精确匹配UserService类
基本上execution能用的通配符within都能用。
@within()和@target 切面表达式函数
@within和@target都是传入一个annotation的类,@within是标记的目标annotation的类的派生类也会匹配,但是@target只匹配标记了目标annotation的类,这里就不再描述了。
target()和this() 切面表达式函数
target和this都接受一个类为参数,但是target指定目标类的所有方法织入,但是this会连目标类的代理类方法也会织入,等于说如果我们使用this的话目标类如果有一个引介增强的话,会连同引介增强的方法也会被织入。
@AspectJ支持逻辑运算符
&& 用于和另外一个切点表达式函数 形成交集组合切面,后面会有相应的例子。
|| 用于和另外一个切点表达式函数 形成并集组合切面。
! 代表除了不匹配当前切点表达式函数 匹配所有。
使用逻辑于运算符 实现 切点复合运算
其他运算符同理,但是后面会我们说到使用xml配置的方式去定义切点,所以&& || 这些符号 spring提供了其他的表达,分别是 &&的替代and ||的替代or !的替代not
命名切点
我们可以将切点分开命名,这个比较难说明,我们用一个例子举例就明白了,使用@Pointcut
然后我们在Aspect类中使用pointcut
最后被注入的方法是以下的调用方法:
JointPoint连接点信息:
有两个主要的类一个是JoinPoint 另外一个是 ProceedingJoinPoint, 其中ProceedingJoinPoint 是应用于@Around增强的,因为他有两个执行目标方法的接口,proceed() 和可以改变其参数的proceed(Object[] args)
JointPoint的主要方法有:
Object[] getArgs() : 获得目标方法的参数
Signature getSignature() : 获得连接点的方法签名对象
getTarget() : 获得连接点所在的目标对象
getThis() : 获得连接点代理对象本身
注意:JointPoint可以应用到所有的增强方法,在要添加其参数在增强方法即可。
ProceedingJoinPoint 继承了JoinPoint子接口,上面也有说过比JointPoint多了两个执行方法分别是:
Object proceed() throws Throwable
Object proceed(Object[] args) throws Throwable
以下使用环绕增强进行演示:
main方法调用已经输出结果:
class : com.maxfunner.service.impl.UserService
method : login
args : TONY PWD
Login : TONY, pwd : PWD
result : null
绑定连接点方法入参:
除了使用JointPoint我们还可以使用绑定连点方法入参数的方式获得参数,下面使用一个例子进行演示:
main方法代码已经输出结果:
>>>>>>>>>>>>proxy name : TONY , pwd : PWD
Login : TONY, pwd : PWD
通过this()或target绑定目标代理对象
使用方法和上面的arg()绑定参数一致
注解对象绑定
使用MyProxy标记UserService类并运行可见已经成功绑定
输出结果:
>>>>>>>>>>>>proxy name : class com.sun.proxy.$Proxy4
Login : TONY, pwd : PWD
返回值绑定
然后我们尝试调用UserService 中的getUserLevel的方法
输出结果:
afterReturning result : LEVEL 10
抛出的异常绑定
在我们使用@AspectJ切面的前提是,我们在配置文件中启动aspectJ的自动代理:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/beans/spring-aop.xsd"> <!-- 业务service --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" />
<context:component-scan base-package="com.maxfunner" /><!-- 基于@AspectJ切面的驱动器 --> <aop:aspectj-autoproxy proxy-target-class="true" /></beans>
其中asp:aspectj-autoproxy标签实际上使用 org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator 进行代理自动创建的,所以如果不想引入aop命名空间 直接 使用 <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" /> 也是等价的。当然我们这里也可以使用 proxy-target-class="true"
这个也不用多说了,就是使用CGlib直接对模板对象生成虚拟子类进行代理。
然后我们创建一个类并使用@Aspect进行定义切点和增强(注意我们在@Before中的参数 execution 是比较常用的切点描述方法,后面会有更加详细的讲解):
@Component @Aspect public class AspectExample { @Before("execution(* login(..))") public void begin(){ System.out.println(">>>>>>>>>>>>begin login"); } }
然后我们使用正常获取bean的方式进行调用,发现AspectExample.begin方法已经织入到userService中的login方法当中
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); userService.login("TONY","PWD"); }
刚刚我们可以发现我们在@Before里面使用了execution,这个是属于切点表达式函数的一种,而且我们在execution里面使用了如.. *等这些通配符,当然aspectJ的切点表达式函数远远不止这些,还有以下这些都属于aspectJ的切点表达式函数,在本文后面会逐一举例说明(包括有:execution,@annotation,args,@args,within,target,@within,@target,this),目前先介绍表达式的通配符。
@AspectJ支持3种通配符:
* 匹配任意字符,但是只能匹配一个元素,例如一个类、一个参数、一个方法。
.. 匹配任意字符,可以匹配任意多个元素。表示类的时候,必须与*联合使用,但是在表示方法入参的时候可以表示多个参数而不需要联合*一起使用。
+ 表示类以下的所有子类,所以这个必须联合类来使用,而且必须定义在类名的后面,例如:UserService+
刚刚我们使用了@Before 增强,然而Aspect提供了一下的的增强:
@Before : 前置增强,相当于BeforeAdvice,其annotation成员有:
value - > 定义切点
argNames - > 由于无法通过Java反射获得方法的入参名称,所以如果在Java编译未启动调试信息或者需要在运行解析切点,就必须通过这个成员指定所标注的增强方法的参数名。
@AfterReturning : 后置增强,相当于AfterReturningAdvice,其annotation成员如下:
pointcut - >表示切点信息,如果定义了pointcut会覆盖value的属性,和value功能一样
value - > 定义切点
argNames - > 由于无法通过Java反射获得方法的入参名称,所以如果在Java编译未启动调试信息或者需要在运行解析切点,就必须通过这个成员指定所标注的增强方法的参数名。
@Around 环绕增强,相当于MethodInterceptor,Around注解类拥有两个成员
value - > 定义切点
argNames - > 由于无法通过Java反射获得方法的入参名称,所以如果在Java编译未启动调试信息或者需要在运行解析切点,就必须通过这个成员指定所标注的增强方法的参数名。
@After Final增强,这个增强比较提别,没有一个相对应之前用过的增强类,@After 可以看做 ThrowsAdvice和 AfterReturningAdvice的混合,就是说无论正常推出还是抛出异常,这个增强方法都会被执行,一般用于释放资源,相当于tyr{}finally{}的控制流。@Afer注解用于成员如下:
value - > 定义切点
argNames - > 由于无法通过Java反射获得方法的入参名称,所以如果在Java编译未启动调试信息或者需要在运行解析切点,就必须通过这个成员指定所标注的增强方法的参数名。
@DecareParents 引介增强,相当于IntroductionInterceptor, @DecareParents 注解类拥有两个成员:
value - > 定义切点
defaultImpl -> 默认的接口实现
由于DecareParents跟其他的增强使用方式不一样,所以以下会有相应的解释
@DecareParents 引介增强的使用:
同样我们使用UserService类,同时我们新建一个AccountSecurity的接口,然后提供一个resetPassword()的方法,同时我们新建AccountSecurityImpl类,同时通过@DecareParents 让UserService的proxy类implement于AccountSecurity接口,同时使用AccountSecurityImpl作为resetPassword等方法的实现:
以下是AccountSecurity 和 AccountSecurityImpl
public interface AccountSecurity { void resetPassword(String account,String newPassword); }
public class AccountSecurityImpl implements AccountSecurity { public void resetPassword(String account, String newPassword) { if(newPassword == null || newPassword.length() < 6){ System.out.println("accout : " + account + " , reset password fail."); return; } System.out.println("account : " + account + " , reset password success."); } }
创建一个Aspect:
public class AccountSecurityAspect { @DeclareParents(value = "com.maxfunner.service.impl.UserService",defaultImpl = AccountSecurityImpl.class) public AccountSecurity accountSecurity; //这个是目标接口,defaultImpl是目标实现 }
@Aspect
@Component
public class AccountSecurityAspect { @DeclareParents(value = "com.maxfunner.service.impl.UserService",defaultImpl = AccountSecurityImpl.class) public AccountSecurity accountSecurity; //这个是目标接口,defaultImpl是目标实现 }
配置文件和上文贴出的代码一直,然后我们尝试调用UserService的代理:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); userService.login("TONY","PWD"); AccountSecurity accountSecurity = (AccountSecurity) userService; accountSecurity.resetPassword("TONY","NEW_PWD"); }
输出如下:
Login : TONY, pwd : PWD
account : TONY , reset password success.
以上通配符和Aspect的增强annotation描述 摘录于《Spring 3.x 企业应用开发实战》。
首先我们先了解如何使用aspectJ的切点表达式函数,下面将会一一介绍:
@annotation() 切面表达式函数
函数中的参数为一个annotation类,定义了该annotation的类方法都会进行织入。下面我们将会自定义一个annotation类名为MyProxy,然后分别在AdminService 和 UserService两个类中标记这个@MyProxy,最后测试效果:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyProxy { }
把这个@MyProxy 标记的AdminService.login方法 和 UserService.register方法中:
public class AdminService { @MyProxy public void login(String username,String password){ System.out.println("Admin Login : " + username + ", pwd : " + password); } public void register(String username,String password){ System.out.println("Admin register : " + username + " , pwd " + password); } }
public class UserService { public void login(String username,String password){ System.out.println("Login : " + username + ", pwd : " + password); } @MyProxy public void register(String username,String password){ System.out.println("register : " + username + " , pwd " + password); } }
我也做个一个测试,尝试将@MyProxy直接标记在类中,但是并没有进行织入,说明这个@annotation的表达式函数只对方法有效,有兴趣的也可以测试一下,当然在定义 MyProxy annotation类的时候记得要在@target添加ElementType.Type,这个是annotation的知识我就不多说了,有兴趣可以百度一下annotation的相关知识。
然后我们定义Aspect类(其实我也不知道应该如何称呼这种类,先称它为aspect类吧)
@Component @Aspect public class AnnotationAspect { @AfterReturning("@annotation(com.maxfunner.annotation.MyProxy)") public void afterReturning(){ System.out.println("afterReturning annocation test !"); } }
注意我们使用的增强是@AfterReturning 所以是在方法执行之后调用。
现在我们在main方法测试并输出结果:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); AdminService adminService = (AdminService) context.getBean("adminService"); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); }
输出结果中可以看到,只有标记了@MyProxy的类方法才会被织入:
Login : TONY, pwd : PWD
register : TONY , pwd PWD
afterReturning annocation test !
Admin Login : TONY, pwd : PWD
afterReturning annocation test !
Admin register : TONY , pwd PWD
最后我贴出一下本文所有实验所用的不变配置,之后的本文的实验将不会再贴出applicationContext.xml的配置文件:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 业务service --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" /> <context:component-scan base-package="com.maxfunner" /> <!-- 基于@AspectJ切面的驱动器 --> <aop:aspectj-autoproxy proxy-target-class="true" /> </beans>
execution() 切面表达式函数
execution 切面表达式函数是最为常用的表达式函数,语法如下:
execution(<修饰符模式[可选]> <返回类型> <方法名>(<参数>)),以下将列出几个范例:
1、execution(public * *(..)) :定义一个方法修饰符为public的 不限制返回类型 不限制方法名 不限制参数的 方法。
2、execution(* *Service(..) ):定义不限制修饰符(因为例子中忽略没有写) 不限方法的返回类型 方法名以Service结尾 不限参数的 所有方法。
3、execution(* com.tony.service.UserService.*(..)) : 定义com.tony.service.UserService类下的所有方法
4、execution(* com.tony.service.UserService+.*(..)) : 定义com.tony.service.UserService类以及其子类下的所有方法
5、execution(* con.tony.service..*(..)): 定义com.tony.service包下以及其子孙包的所有类的所有方法被织入
6、execution(* com.tony.service.*(..)): 定义com.tony.service包下的所有类的所有方法被织入,但不包括其子孙包的类。
7、execution(* login(String,String)): 定义方法名为login 其参数为两个String类型的方法被织入。
8、execution(* getUserId(String,*)):定义方法名为getUserId 其方法参数有两个一个是String 另外一个为任意类型的 方法被织入。
9、execution(* resetPassword(String,..)):定义方法名为resetPassword 其方法参数有多个第一个参数为String 其余的参数任何类型 的方法被织入。
10、execution(* saveUserInDB(User+)):定义方法名为saveUserInDB 其方法参数是一个User对象获取是User派生的对象。
arg() 切面表达式函数
args接受一个类名,表示目标方法的入参类型为目标类型或者目标类型的派生类型:
@Component @Aspect public class ArgsAspect { @After("args(com.maxfunner.entity.User)") public void after(){ System.out.println(">>>>>>Args Aspect test!!!"); } }
以上代码 匹配所有参数只有一个User的方法,如果想匹配方法中第一个参数为User 其他参数不限制可以同样式样aspectJ提供的通配符:
@Component @Aspect public class ArgsAspect { @After("args(com.maxfunner.entity.User,..)") public void after(){ System.out.println(">>>>>>Args Aspect test!!!"); } }
@arg() 切面表达式函数
@arg 接受annotation类,匹配方法中的标记了目标annotation的类,下面是一个范例:
@Component @Aspect public class ArgsAspect { @After("@args(com.maxfunner.annotation.MyProxy,..)") public void after(){ System.out.println(">>>>>>@Args Aspect test!!!"); } }
使用MyProxy标记User类
@MyProxy public class User implements BeanFactoryAware,BeanNameAware,InitializingBean,DisposableBean {
以下方法会匹配:
public void testA(User user){ System.out.println("testA"); } public void testB(User user,String username){ System.out.println("testB"); }
within() 切面表达式函数
within和execution差不多,但是within最小范围匹配到类,而execution是匹配到方法,所以说execution的功能基本涵盖within。以下是一些例子:
1、within(com.tony.service.*)匹配com.tony.service包一下的所有类
2、within(com.tony.service.UserService) 精确匹配UserService类
基本上execution能用的通配符within都能用。
@within()和@target 切面表达式函数
@within和@target都是传入一个annotation的类,@within是标记的目标annotation的类的派生类也会匹配,但是@target只匹配标记了目标annotation的类,这里就不再描述了。
target()和this() 切面表达式函数
target和this都接受一个类为参数,但是target指定目标类的所有方法织入,但是this会连目标类的代理类方法也会织入,等于说如果我们使用this的话目标类如果有一个引介增强的话,会连同引介增强的方法也会被织入。
@AspectJ支持逻辑运算符
&& 用于和另外一个切点表达式函数 形成交集组合切面,后面会有相应的例子。
|| 用于和另外一个切点表达式函数 形成并集组合切面。
! 代表除了不匹配当前切点表达式函数 匹配所有。
使用逻辑于运算符 实现 切点复合运算
@Component @Aspect public class ArgsAspect { @After("@args(com.maxfunner.annotation.MyProxy,..) && execution(* com.maxfunner.service.*(..))") public void after(){ System.out.println(">>>>>>@Args Aspect test!!!"); } }
其他运算符同理,但是后面会我们说到使用xml配置的方式去定义切点,所以&& || 这些符号 spring提供了其他的表达,分别是 &&的替代and ||的替代or !的替代not
命名切点
我们可以将切点分开命名,这个比较难说明,我们用一个例子举例就明白了,使用@Pointcut
@Aspect public class MyPointcut { @Pointcut("execution(* com.maxfunner.service..*(..))") public void allServicemMethod(){} @Pointcut("args(com.maxfunner.entity.User+,..)") public void userArgPointcut(){} }
然后我们在Aspect类中使用pointcut
@Component @Aspect public class AspectExample { @Before("com.maxfunner.pointcut.MyPointcut.allServicemMethod() " + "&& com.maxfunner.pointcut.MyPointcut.userArgPointcut()") public void begin(){ System.out.println(">>>>>>>>>>>>begin"); } }
最后被注入的方法是以下的调用方法:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); userService.testA(new User()); }
JointPoint连接点信息:
有两个主要的类一个是JoinPoint 另外一个是 ProceedingJoinPoint, 其中ProceedingJoinPoint 是应用于@Around增强的,因为他有两个执行目标方法的接口,proceed() 和可以改变其参数的proceed(Object[] args)
JointPoint的主要方法有:
Object[] getArgs() : 获得目标方法的参数
Signature getSignature() : 获得连接点的方法签名对象
getTarget() : 获得连接点所在的目标对象
getThis() : 获得连接点代理对象本身
注意:JointPoint可以应用到所有的增强方法,在要添加其参数在增强方法即可。
ProceedingJoinPoint 继承了JoinPoint子接口,上面也有说过比JointPoint多了两个执行方法分别是:
Object proceed() throws Throwable
Object proceed(Object[] args) throws Throwable
以下使用环绕增强进行演示:
@Component @Aspect public class AroundAspect { @Around("execution(* com.maxfunner.service..*(..))") public void LogProxy(ProceedingJoinPoint joinPoint) { System.out.println("class : " + joinPoint.getSignature().getDeclaringTypeName()); System.out.println("method : " + joinPoint.getSignature().getName()); System.out.println("args : " + getArgsStr(joinPoint.getArgs())); Object result = null; try { result = joinPoint.proceed(); System.out.println("result : " + result); } catch (Throwable throwable) { throwable.printStackTrace(); } } public String getArgsStr(Object[] args) { StringBuffer stringBuffer = new StringBuffer(); for (Object item : args) { stringBuffer.append(item.toString() + " "); } return stringBuffer.toString(); } }
main方法调用已经输出结果:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); userService.login("TONY","PWD"); }
class : com.maxfunner.service.impl.UserService
method : login
args : TONY PWD
Login : TONY, pwd : PWD
result : null
绑定连接点方法入参:
除了使用JointPoint我们还可以使用绑定连点方法入参数的方式获得参数,下面使用一个例子进行演示:
@Component @Aspect public class AspectExample { //args的顺序和目标方法一致 @Before(value = "execution(* *(String,String)) && args(name,pwd)") //增强方法的入参的方法可以和目标的方法不一致,但是名字必须要和args定义的一致 public void begin(String pwd,String name){ System.out.println(">>>>>>>>>>>>proxy name : " + name + " , pwd : " + pwd); } }
main方法代码已经输出结果:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); userService.login("TONY","PWD"); }
>>>>>>>>>>>>proxy name : TONY , pwd : PWD
Login : TONY, pwd : PWD
通过this()或target绑定目标代理对象
使用方法和上面的arg()绑定参数一致
@Component @Aspect public class AspectExample { //args的顺序和目标方法一致 @Before(value = "execution(* *(String,String)) && this(proxyObj)") //增强方法的入参的方法可以和目标的方法不一致,但是名字必须要和args定义的一致 public void begin(Object proxyObj){ System.out.println(">>>>>>>>>>>>proxy name : " + proxyObj.getClass()); } }
注解对象绑定
@Component @Aspect public class AspectExample { //args的顺序和目标方法一致 @Before(value = "@within(proxy)") //增强方法的入参的方法可以和目标的方法不一致,但是名字必须要和args定义的一致 public void begin(MyProxy proxy){ System.out.println(">>>>>>>>>>>>proxy name : " + proxy.getClass()); } }
使用MyProxy标记UserService类并运行可见已经成功绑定
@MyProxy public class UserService {
输出结果:
>>>>>>>>>>>>proxy name : class com.sun.proxy.$Proxy4
Login : TONY, pwd : PWD
返回值绑定
@Component @Aspect public class AnnotationAspect { @AfterReturning(value = "@within(com.maxfunner.annotation.MyProxy)",returning = "result") public void afterReturning(Object result){ System.out.println("afterReturning result : " + result); } }
然后我们尝试调用UserService 中的getUserLevel的方法
@MyProxy public class UserService {
public String getUserLevel(String userId){
return "LEVEL 10";
}
}
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); userService.getUserLevel("ABC"); }
输出结果:
afterReturning result : LEVEL 10
抛出的异常绑定
@Component @Aspect public class ThrowingAspect { @AfterThrowing(value = "execution(* com.maxfunner..*(..))",throwing = "e") public void throwingTest(Exception e){ e.printStackTrace(); } }
相关文章推荐
- Spring2.5 注解 Aspect AOP (转)
- spring面向切面编程AOP(Aspect-orented programming)
- 利用Spring AOP (aspect) 自定义注解解决日志和签名校验
- Spring--AOP--基本配置(切面Aspect,切入点Pointcut,通知Advice)
- Spring aop Aspect实现Service或Controller日志记录
- SPRING IN ACTION 第4版笔记-第四章Aspect-oriented Spring-001-什么是AOP
- 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-使用工厂创建代理(Using the ProxyFactoryObject to create AOP proxies)
- 05 Spring Aop实例(AOP 如此简单)@Aspect、@Around 注解方式配置
- Spring 嵌套AOP 的顺序问题 指定Aspect优先级
- Spring学习(24)--- AOP之 Aspect instantiation models(aspect实例模式)特别说明
- spring aop实现过程之三Spring AOP中Aspect编织的实现
- SPRING IN ACTION 第4版笔记-第四章ASPECT-ORIENTED SPRING-010-Introduction为类增加新方法@DeclareParents、<aop:declare-parents>
- 马士兵Spring-AOP-Aspect例子使用(1)
- Spring aop(Aspect Oriented Programming:面向切面编程)入门
- SpringBoot AOP 拦截器 Aspect
- 05 Spring Aop实例(AOP 如此简单)@Aspect、@Around 注解方式配置
- spring aop :logaspect配置
- spring Aop中aop:advisor 与 aop:aspect的区别
- Spring(5)AOP-Aspect Oriented Programming
- 05 Spring Aop实例(AOP 如此简单)@Aspect、@Around 注解方式配置