1 采用AspectJ模拟事务提交
定义AspectJ的步骤如下
1.采用注解标@AspectJ标注对应的类
2.采用注解声明切点
3.实现增强
声明接口
public interface IOrders { public List<Orders> queryAllOrders() throws Exception; public Orders queryOrder(int id) throws Exception; public int updateOrder(int id) throws Exception; }
实现接口
public class OrderImpl implements IOrders { @Override public List<Orders> queryAllOrders() throws Exception { return Collections.emptyList(); } @Override public Orders queryOrder(int id) throws Exception { return new Orders(); } @Override public int updateOrder(int id) throws Exception { return id; } }
定义领域模式
public class Orders { private String id; private String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
定义AapectJ切面
@Aspect public class OrdersAdvisor { @Around("execution (* com.aspectj..*.query*(..))") //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 public Object adviceOrder(ProceedingJoinPoint pjp) throws Throwable { //获得方法签名 Signature signature = pjp.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; //获得方法名称 String methodName = methodSignature.getMethod().getName(); //获得方法参数 Object[] args = pjp.getArgs(); Object retVal = null; try { System.out.println("方法: " + methodName + "开始事务" ); if (args == null || args.length == 0) { retVal = pjp.proceed(); } else { retVal = pjp.proceed(args); } System.out.println("方法: " + methodName + "事务成功" ); } catch (Exception e) { System.out.println("方法: " + methodName + "事务失败" ); } return retVal; } }
ProceedingJointPoint很重要,采用该类的proceed()/proceed(Object[] args)方法可以执行目标方法,如果想在增强中执行目标方法,建议将增强方法的第一个参数设置为ProceedingJointPoint
采用编码方式测试AspectJ步骤如下
1.获得AspectJProxyFactory实例
2.设置目标对象
3.设置切面
3.获得代理对象并调用对应的方法
IOrders order = new OrderImpl(); AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(order); proxyFactory.addAspect(OrdersAdvisor.class); Object obj = ((IOrders)proxyFactory.getProxy()).queryOrder(2);
采用配置方式测试步骤如下
1.声明目标类
2.声明自定义切面
3.配置AnnotationAwareAspectJAutoProxyCreator或者开启基于@AspectJ切面的注解器。建议开启AspectJ注解器
<bean id="order" class="com.aspectj.impl.OrderImpl"></bean> <bean id="advisor" class="com.aspectj.advisor.OrdersAdvisor"></bean> <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
或者采用如下的方式
<!--开启基于@Aspectj切面的驱动器 proxy-target-class设置使用哪种代理方式true:CGLib,fasle:JDK动态代理--> <aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="order" class="com.aspectj.impl.OrderImpl"></bean> <bean id="advisor" class="com.aspectj.advisor.OrdersAdvisor"></bean>
IOrders order = (IOrders) applicationContext.getBean("order"); order.queryOrder(2);
可以看出基于注解方式定义切面,需要配置的很少且没有和Spring框架紧密结合(不需要实现框架的接口)
2 AspectJ语法
2.1 切点分类
根据AspectJ描述对象的不同可以将切点表达函数分为4中
1.方法切点函数:是最常用的函数,通过描述目标方法的信息定义定义连接点
2.方法入参切点函数:通过描述目标方法入参类型定义连接点
3.目标类切点函数:通过描述目标类类型信息定义连接点
3.代理类切点函数:通过描述目标类的代理类定义连接点
采用表格中的函数,就可以定义复合在自己的切点以及增强
2.2 切点通配符
* | 匹配任意字符。上下文中只匹配一个元素eg访问修饰符,返回值,方法名等 |
.. | 匹配任意字符。可以匹配上下文中的多个元素eg访问修饰符,返回值,方法名等。但是在匹配类时,需要和*连用,表示入参时候可以单独使用 |
+ | 按类型匹配指定类的所有类,必须跟在类名的后面。表示类自身以及子类 |
在所有的切点函数中支持所有通配符的有execution()和within,仅支持+的有args,target,this。不支持通配符的有@args,@target,@annotation,@whithin
2.3 切点运算符
|| 或者or | 切点或运算 |
&& 或者 and | 切点与运算 |
! 或者not | 切点取反运算 |
2.4 切点函数
2.4.1 @annotation
使用@annotation(注解类型),表示标注了某注解类的所有连接点,这些连接点都匹配该切点
1.定义注解类
public @interface DaoAnnotation { public String value() default ""; public int id() default 0; }
2.编写AspectJ切面,这里使用现有的切面类OrdersAdvisor
3.实现对应的接口
4.配置对应的Bean
<aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="target" class="com.annotation.dao.OrderDao"></bean> <bean id="advisor" class="com.aspectj.advisor.OrdersAdvisor"></bean>
5.测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/com/annotation/config/config.xml"); OrderDao orderDao = (OrderDao) applicationContext.getBean("target"); try { System.out.printf(orderDao.updateOrder(1) + ""); } catch (Exception e) { e.printStackTrace(); }
6 问题
1.建议采用aspectjweaver-1.6.12.jar,否则出现:@annotation pointcut expression is only supported at Java 5 compliance level
2.建议采用CGLib代理方式即在xml中配置proxy-taget-calss=true否则出现:java.lang.ClassCastException: com.sun.proxy.$Proxy5 cannot be cast to com.annotation.dao.OrderDao
2.4.2 execution
使用“execution(方法表达式)”匹配方法执行,文章开始处采用的方式。该方式是最常用的
模式为execution([访问修饰符] 返回类型模式 方法名模式(参数模式))
返回类型模式 方法名模式 参数模式这三个模式是必须的
@Aspect public class OrdersAdvisor { @Around("@annotation(com.annotation.annotation.DaoAnnotation)") //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 public Object adviceOrder(ProceedingJoinPoint pjp) throws Throwable {....}
模式 | 描述 |
public * *(..) | 任何公共方法 |
* cn.dao..IUserDao.*() | cn.test包及所有子包下IUserDap接口中的任何无参方法 |
* cn.dao..*.*(..) | cn.dao包及所有子包下任何类的任何方法 |
* cn.dao..IUserDao.*(*) | cn.dao包及所有子包下IUserDap接口的任何只有一个参数方法 |
* (!cn.dao..IUserDao+).*(..) | 非cn.dao包及所有子包下IUserDao接口及子类型的任何方法 |
* cn.dao..IUserDao+.*() | cn.dao包及所有子包下IUserDap接口及子类型的的任何无参方法 |
* cn.dao..IUserDao*.test*(java.util.Date) | cn.dao包及所有子包下IUserDao前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法签名的参数类型进行匹配的,而不是根据执行时传入的参数类型决定的 如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的; |
* cn.dao..IUserDao*.test*(..) throws IllegalArgumentException, ArrayIndexOutOfBoundsException | cn.dao包及所有子包下IUserDao前缀类型的的任何方法,且抛出IllegalArgumentException和ArrayIndexOutOfBoundsException异常 |
* (cn.dao..IUserDao+ && java.io.Serializable+).*(..) | 任何实现了cn.dao包及所有子包下IUserDap接口和java.io.Serializable接口的类型的任何方法 |
@java.lang.Deprecated * *(..) | 任何持有@java.lang.Deprecated注解的方法 |
@java.lang.Deprecated @cn.annotation..Secure * *(..) | 任何持有@java.lang.Deprecated和@cn.annotation..Secure注解的方法 |
@(java.lang.Deprecated || cn.annotation..Secure) * *(..) | 任何持有@java.lang.Deprecated或@ cn.annotation..Secure注解的方法 |
(@cn.annotation..Secure *) *(..) | 任何返回值类型持有@cn.annotation..Secure的方法 |
* (@cn.annotation..Secure *).*(..) | 任何定义方法的类型持有@cn.annotation..Secure的方法 |
* *(@cn.annotation..Secure (*) , @cn.annotation..Secure (*)) | 任何签名带有两个参数的方法,且这个两个参数都被@ Secure标记了, 如public void test(@Secure String str1, @Secure String str1); |
* *((@ cn.annotation..Secure *))或 * *(@ cn.annotation..Secure *) | 任何带有一个参数的方法,且该参数类型持有@ cn.annotation..Secure; 如public void test(Model model);且Model类上持有@Secure注解 |
* *( @cn.annotation..Secure (@cn.annotation..Secure *) , @ cn.annotation..Secure (@cn.annotation..Secure *)) | 任何带有两个参数的方法,且这两个参数都被@ cn.annotation..Secure标记了;且这两个参数的类型上都持有@ cn.annotation..Secure; |
* *( java.util.Map<cn.dao..Model, cn.dao..Model> , ..) | 任何带有一个java.util.Map参数的方法,且该参数类型是以< cn.dao..Model, cn.dao..Model >为泛型参数;注意只匹配第一个参数为java.util.Map,不包括子类型; 如public void test(HashMap<Model, Model> map, String str);将不匹配,必须使用“* *( java.util.HashMap<cn.dao..Model,cn.dao..Model> , ..)”进行匹配; 而public void test(Map map, int i);也将不匹配,因为泛型参数不匹配 |
* *(java.util.Collection<@cn.dao..Secure *>) | 任何带有一个参数(类型为java.util.Collection)的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.dao..Secure注解; 如public void test(Collection<Model> collection);Model类型上持有@cn.dao..Secure |
* *(java.util.Set<? extends HashMap>) | 任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型继承与HashMap; Spring AOP目前测试不能正常工作 |
* *(java.util.List<? super HashMap>) | 任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型是HashMap的基类型;如public voi test(Map map); Spring AOP目前测试不能正常工作 |
* *(*<@cn.dao..Secure *>) | 任何带有一个参数的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.dao..Secure注解; Spring AOP目前测试不能正常工作 |
2.4.3 args
args(参数类型)表示目标类方法入参类型是指定的类以及指定类的子类。匹配的仅仅是方法入参类型,不匹配方法签名。属于动态切点,开销很大
@Aspect public class OrdersAdvisor { @Around("args(com.demo.entry.User)") //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 public Object adviceOrder(ProceedingJoinPoint pjp) throws Throwable {}
public List<Orders> queryOrdersForUser(User user) throws Exception;
<aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="target" class="com.annotation.dao.OrderDao"></bean> <bean id="advisor" class="com.aspectj.advisor.OrdersAdvisor"></bean>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/com/annotation/config/config.xml"); OrderDao orderDao = (OrderDao) applicationContext.getBean("target"); try { System.out.printf(orderDao.queryOrdersForUser(new User()).size() + ""); } catch (Exception e) { e.printStackTrace(); }
2.4.4 @args
@args(注解类型[s])目标类入参中使用了指定的注解类型,匹配该切点。也就说说目标类参数上使用了指定的注解类型
如下为类的继承图
入参类型点:方法签名在类继承树中的位置
注解点:标注了注解的类在类继承树中的位置
@Component(value = "t0") public class T0 { }
@Component(value = "t1") public class T1 extends T0 { public void fun(T1 t) {//入参类型点 System.out.println(t.getClass().getName() + " fun executed"); } }
@Component(value = "t2") @MyAnnotation//注解点 public class T2 extends T1 { }
@Component(value = "t3") public class T3 extends T2 { }
@Aspect public class TAdvisor { @Around("@args(com.args.annotation.MyAnnotation)") public void advice(ProceedingJoinPoint point) { System.out.println(((MethodSignature)point.getSignature()).getMethod().getDeclaringClass().getName() + "增强执行了"); } }
ApplicationContext ctx = new ClassPathXmlApplicationContext("/com/annotation/config/config.xml"); T0 t0 = ctx.getBean("t0", T0.class); T1 t1 = ctx.getBean("t1", T1.class); T2 t2 = ctx.getBean("t2", T2.class); T3 t3 = ctx.getBean("t3", T3.class); // 因t1中的fun入参为t2,且注解标注在了T2类上,t3又是t2的子类,所以 下面两个调用都会织入增强 t1.fun(t2); t1.fun(t3); }
输出验证后增强能够被织入
将注解点放在T0中,增强无法织入。
在类继承树中注解点高于入参类型点,目标方法不可能匹配切点
在类继承树中注解点低于入参类型点,则注解点所在的类以及子类都匹配该切点。
2.4.5 within
within(类匹配模式)针对目标类而言,不是针对类型,最小范围是类
@Aspect public class OrdersAdvisor { @Around("within(com.annotation.dao.OrderDao)") //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 public Object adviceOrder(ProceedingJoinPoint pjp) throws Throwable {}
<aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="target" class="com.annotation.dao.OrderDao"></bean> <bean id="advisor" class="com.aspectj.advisor.OrdersAdvisor"></bean>
模式 | 描述 |
within(cn.dao..*) | cn.dao包及子包下的任何方法执行 |
within(cn.dao..IUserDao+) | cn.dao包或所有子包下IUserDao类型及子类型的任何方法 |
within(@cn.dao..Secure *) | 持有cn.dao..Secure注解的任何类型的任何方法 必须是在目标对象上声明这个注解,在接口上声明的对它不起作用 |
2.4.6 @within
@within(注解类型)匹配任意标注了指定注解类型的类
@Aspect public class OrdersAdvisor { @Around("@within(com.annotation.annotation.DaoAnnotation)") //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 public Object adviceOrder(ProceedingJoinPoint pjp) throws Throwable {}
@DaoAnnotation public class OrderDao implements IOrders {}
2.4.7 @target
@target(注解类型)匹配任意标注了指定注解类型的类以及子类
@Aspect public class OrdersAdvisor { @Around("@target(com.annotation.annotation.DaoAnnotation)") //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 public Object adviceOrder(ProceedingJoinPoint pjp) throws Throwable {}
@DaoAnnotation public class OrderDao implements IOrders {}
2.4.8 target
target(类名)按照目标类是否为给定类的类型匹配切点,如果匹配那么当前类以及子类的所有连接点都匹配该切点和this等效
@Aspect public class OrdersAdvisor { @Around("target(com.annotation.dao.OrderDao)") //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 public Object adviceOrder(ProceedingJoinPoint pjp) throws Throwable {}
2.4.9 this
this(类型)按照代理类是否为给定类的类型匹配切点,与target灯效
@Aspect public class OrdersAdvisor { @Around("this(com.annotation.dao.OrderDao)") //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 public Object adviceOrder(ProceedingJoinPoint pjp) throws Throwable {}
3 AspecJ切点和原生切点对应关系
AspectJ切点 | AspectJ切点参数 | 原生切点 | 说明 | 示例 |
---|---|---|---|---|
@Before | value:用于定义切点 argNames:无法采用反射获取入参信息通过该中方式绑定入参信息argNames中罗列出来的参数名称必须 和目标方法中的参数名称相同 也就说通过参数名绑定参数 | BeforeAdvice | AspectJ提供了参数绑定机制 以便获取参数信息 | @Before(value="com.aspectj..*.*Dao", argNames="name,id") |
@AfterReturning | value:用于定义切点 pointcut:和value表示的含义一致如果声明了该属性,那么将会覆盖value的信息 argNames:无法采用反射获取入参信息通过该中方式绑定入参信息 argNames中罗列出来的参数名称必须 和目标方法中的参数名称相同 也就说通过参数名绑定参数 returning:将目标方法的返回值绑定到增强方法上 | AfterReturningAdvice | 只有在目标方法正确被执行后才会织入增强 | @Before(value="com.aspectj..*.*Dao", pointcut="com.aspectj..*.*Service", argNames="name,id", returning="retVal") |
@Around | value:用于定义切点 argNames:无法采用反射获取入参信息 通过该中方式绑定入参信息argNames中罗列出来的参数名称必须和目标方法中的参数名称相同也就说通过参数名绑定参数 | MethodInterceptor | @Around(value="com.aspectj..*.*Dao", argNames="name,id") | |
@AfterThrowing | value:用于定义切点 pointcut:和value表示的含义一致如果声明了该属性,那么将会覆盖value的信息 argNames:无法采用反射获取入参信息 通过该中方式绑定入参信息argNames中罗列出来的参数名称必须和目标方法中的参数名称相同也就说通过参数名绑定参数 throwing:将目标方法的异常信息绑定到增强方法上 | ThrowAdvice | @Before(value="com.aspectj..*.*Dao", pointcut="com.aspectj..*.*Service", argNames="name,id", throwing="exp").throwing的值需要和增强方法中定义的参数名称一致 | |
@After | value:用于定义切点 argNames:无法采用反射获取入参信息 通过该中方式绑定入参信息argNames中罗列出来的参数名称必须和目标方法中的参数名称相同也就说通过参数名绑定参数 | AfterReturningAdvice + ThrowAdvice | 相当于Final增强,不管是否抛出异常目标方法被执行,一般用于释放资源 | @After(value="com.aspectj..*.*Dao", argNames="name,id") |
@DeclearParents | value:用于定义切点,表示在那个目标类上添加引介增强 defaultImpl:接口默认实例类 | 相当于引介增强IntroductionInterceptor | @DeclearParents(value="com.dao.UserDao", defaultImpl="com.advisor,UserDaoAdvisor.class") |
引介增强示例
@Aspect public class OrdersAdvisor { @DeclareParents(value = "com.annotation.dao.OrderDao", defaultImpl = com.demo.impl.UserDao.class) //该处必须是接口并且DeclareParents必须声明在field上 public IUserDao userDao;
OrderDao orderDao = applicationContext.getBean("target", OrderDao.class); try { // System.out.printf(orderDao.queryOrdersForUser(new User()).size() + ""); System.out.println(((IUserDao) orderDao).queryUserByName("ss"));//成功转型,给OrderDao动态添加了IUserDao接口的实现 } catch (Exception e) { e.printStackTrace(); }
4 切点定义
采用如下模式定义的切点只能在切丁声明处使用,外部无法使用。这样的切点我们成为匿名切点。增强了对切点的保护性,但是失去可公用性
@Around("execution (* com.aspectj..*.query*(..))")
切点的定义和类的定义基本一致,因为切点的定义仅仅是利用的是方法名以及访问修饰符等信息,所以习惯上将方法的返回值定义为void,方法体为空
4.1 定义切点
public class OrderDaoPointcut { @Pointcut("execution (* com.aspectj..*.query*(..))") //此处仅仅是切点的定义,具体增强逻辑需要到AspectJ切面中实现 public void query(){} }
4.2 使用切点
@Aspect public class OrdersAdvisor { @Around("com.annotation.pointcut.OrderDaoPointcut.query()") public Object adviceOrder(ProceedingJoinPoint pjp) throws Throwable {}
通过如上的方式完成了切点的定义以及使用。采用该种方式定义的切点可以重复使用。具体的细节如下
1.在方法上采用@Pointcut(切点表达式)的方式声明切点,该方法的特点为无参且无返回值
2.采用AspectJ切面使用自定的增强
5 增强织入的顺序
1.增强在同一个切面中,织入的顺序其实就是在切面中声明的顺序
2.增强在不同的切面中并且这些切面都实现了Ordered接口,那么织入的顺序是:按照顺序执行即顺序号小的优先执行
3.增强在不同的切面中并且这些切面都未实现Ordered接口,那么织入的顺序是不固定的
6 访问连接点信息
6.1 ProceedingJoinPoint
开始的例子中采用该ProceedingJoinPoint#proceed()执行目标方法。该类是JoinPoint的子类提供了两个方法,都可以用来执行目标类的方法
ProceedingJoinPoint#proceed():通过反射执行目标类方法
ProceedingJoinPoint#proceed(Object[] args):和ProceedingJoinPoint#proceed()的作用基本一致,但是此处的args是我们为目标方法定义的新的入参。
6.2 JoinPoint
表示目标类连接点对象。也就说我们可以通过JoinPoint访问连接点的信息。连接点其实就是类中的方法。也就是说我们可以获得方法的参数,方法签名,目标对等
Object[] getArgs():获得连接点运行时入参列表即参数信息
Signature getSignature():获得方法签名,通过签名可以获得目标对象方法的相关信息:方法名,方法入参类型,方法返回值等
Object getTarget():获得目标对象
Object getThis():获得代理对象
其中前两个方法是比较重要的。这两个方法基本上就提供了执行目标方法的所有信息
JoinPoint使用示例
和ProceedingJoinPoint的使用方式基本一致
@Aspect public class OrdersAdvisor { // @DeclareParents(value = "com.annotation.dao.OrderDao", defaultImpl = com.demo.impl.UserDao.class) // //该处必须是接口并且DeclareParents必须声明在field上 // public IUserDao userDao; //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 @Around("com.annotation.pointcut.OrderDaoPointcut.query()") public Object adviceOrder(JoinPoint pjp) throws Throwable { //获得方法签名 Signature signature = pjp.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; //获得方法名称 String methodName = methodSignature.getMethod().getName(); //获得方法参数 Object[] args = pjp.getArgs(); Object retVal = null; return retVal; } }
7 连接点参数绑定
7.1 入参绑定
使用切面函数args(参数类型即类名)绑定运行时参数信息,
我们需要在将args中的name,id绑定到方法签名中的name,id以便可以在增强中获得运行时获得入参数据
@Aspect public class OrdersAdvisor { // @DeclareParents(value = "com.annotation.dao.OrderDao", defaultImpl = com.demo.impl.UserDao.class) // //该处必须是接口并且DeclareParents必须声明在field上 // public IUserDao userDao; //该增强只织入com.aspectj包以及子包中的所有类且方法名以query作为前缀 //如果想要执行目标方法增强方法必须包含ProceedingJoinPoint参数 @Around("com.annotation.pointcut.OrderDaoPointcut.query() || args(name, id,..)") public Object adviceOrder(ProceedingJoinPoint pjp,int id, String name) throws Throwable {}
args(参数类型即类名)接收的是类名,此时给的是字符串。
1.通过args(name,id)中的名称对应的增强方法的参数列表中的name,id,在增强方法中存在类型信息。通过名称对应后获得参数类型。此时args(name, id) --> args(java.lang.String, java.lang.Integer)后者完全符合args函数的定义。可以看出连接点参数位置是由在args函数中参数名称决定的。也就是如果想要实现参数绑定,那么args中声明的参数名称必须要和增强方法中的参数名称相同
2.第一步中通过参数名称完成了名称到类型的转化。该步骤中将会完成参数信息的绑定。
参数类型获得/信息绑定,都是通过名称匹配完成,args中声明的参数名称必须要和增强方法中的参数名称相同
此时我们调用含有入参的目标方法,那么增强就会按照切点规则织入。我们就可以子增强方法中获得入参数据
7.2 绑定代理对象
@Aspect public class OrdersAdvisor { @Around("this(userDao)") public Object adviceOrder(ProceedingJoinPoint pjp,IUserDao userDao) throws Throwable {}
7.3 绑定注解对象
@Aspect public class OrdersAdvisor { @Around("@within(myAnnotation)") public Object adviceOrder(ProceedingJoinPoint pjp,MyAnnotationmyAnnotation) throws Throwable {}
代理对象,注解对象的绑定和参数绑定机制一致
7.4 绑定返回值
@Aspect public class OrdersAdvisor { @AfterReturning(value = "@within(myAnnotation)", returning="retVal") public Object adviceOrder(ProceedingJoinPoint pjp, int retVal) throws Throwable {}需要在增强方法签名中声明返回值,名称必须要和切点中returning定义的名称一致且返回值类型必须要和目标方法一致
7.5 绑定异常信息
@Aspect public class OrdersAdvisor { @AfterThrowing(value = "@within(myAnnotation)", throwing="exp") public Object adviceOrder(ProceedingJoinPoint pjp, IOException exp) throws Throwable {}
需要在增强方法签名中声明异常类型,名称必须要和切点中throwing定义的名称一致且返回。只有当目标方法抛出的异常和增强方法入参中定义的异常类型一致时,增强才会被织入。
8 配置切面
步骤:
1.定义增强:普通的java类即可
2.在xml中声明切点以及增强方法
//定义增强 public class OrderAdvice { //增强方法,增强逻辑的具体的实现 public void adviceMethod() { System.out.println("this my advice method"); } }
<aop:config proxy-target-class="true"> <aop:aspect ref="advice"> <aop:around pointcut="execution (* com.aspectj..*.query*(..)))" method="adviceMethod"></aop:around> </aop:aspect> </aop:config>
<bean id="advice" class="com.annotation.advice.OrderAdvice"></bean>
在配置中同样可以进行切点的元算操作,上面声明的切点没有办法在<aop:config>标签中重复使用,如果要公用切点需要采用配置命名切点
9 配置命名切点
<aop:config proxy-target-class="true"> <aop:aspect ref="advice"> <aop:pointcut id="myPointcut" expression="execution (* com.aspectj..*.query*(..)))"></aop:pointcut> <aop:around pointcut-ref="myPointcut" method="adviceMethod"></aop:around> <aop:before pointcut-ref="myPointcut" method="adviceMethod"></aop:before> <aop:after pointcut-ref="myPointcut" method="adviceMethod"></aop:after> </aop:aspect> </aop:config> <bean id="advice" class="com.annotation.advice.OrderAdvice"></bean>
步骤如下:
1.定义增强 2.定义切点 3.在切面中采用pointcut-ref引用自定义切点.
如上定义的切点只能在当前的<aop:aspect>中使用,采用如下的方式定义切点,那么在整个<aop:config>中都可以使用该切点,采用该中方式必须要保证<aop:pointcut>在<aop:aspect>之前定义。在<aop:config>下还可以定义<aop:advisor>,三者之间的关系为:首先定义<aop:pointcut>然后定义<aop:advisor>最后定义<aop:aspect>
<aop:config proxy-target-class="true"> <aop:pointcut id="myPointcut" expression="execution (* com.aspectj..*.query*(..)))"></aop:pointcut> <aop:aspect ref="advice"> <aop:around pointcut-ref="myPointcut" method="adviceMethod"></aop:around> <aop:before pointcut-ref="myPointcut" method="adviceMethod"></aop:before> <aop:after pointcut-ref="myPointcut" method="adviceMethod"></aop:after> </aop:aspect> </aop:config> <bean id="advice" class="com.annotation.advice.OrderAdvice"></bean>
10 配置增强示例
10.1 后置增强
<aop:config proxy-target-class="true"> <aop:pointcut id="myPointcut" expression="execution (* com.aspectj..*.query*(..)))"></aop:pointcut> <aop:aspect ref="advice"> <aop:after-returning method="adviceMethod" pointcut-ref="myPointcut" returning="retVal"></aop:after-returning> </aop:aspect> </aop:config> <bean id="advice" class="com.annotation.advice.OrderAdvice"></bean>
public class OrderAdvice { //增强方法,增强逻辑的具体的实现 public void adviceMethod(int retVal) { System.out.println("this my advice method"); } }增强方法的入参必须和配置中returning的属性值一致
10.2 环绕增强
<aop:config proxy-target-class="true"> <aop:pointcut id="myPointcut" expression="execution (* com.aspectj..*.query*(..)))"></aop:pointcut> <aop:aspect ref="advice"> <aop:around method="adviceMethod" pointcut-ref="myPointcut"/> </aop:aspect> </aop:config>
public class OrderAdvice { //增强方法,增强逻辑的具体的实现 public void adviceMethod(ProceedingJoinPoint pjp) { System.out.println("this my advice method"); } }
10.3 抛出异常增强
<aop:config proxy-target-class="true"> <aop:pointcut id="myPointcut" expression="execution (* com.aspectj..*.query*(..)))"></aop:pointcut> <aop:aspect ref="advice"> <aop:after-throwing method="adviceMethod" pointcut-ref="myPointcut" throwing="npe"/> </aop:aspect> </aop:config> <bean id="advice" class="com.annotation.advice.OrderAdvice"></bean>
public class OrderAdvice { //增强方法,增强逻辑的具体的实现 public void adviceMethod(NullPointerException npe) { System.out.println("this my advice method"); } }throwing配置信息需要和增强方法的入参名称一致,也就说在发生何种异常的情况下触发该增强
10.4 Final增强
<aop:config proxy-target-class="true"> <aop:pointcut id="myPointcut" expression="execution (* com.aspectj..*.query*(..)))"></aop:pointcut> <aop:aspect ref="advice"> <aop:after method="adviceMethod" pointcut-ref="myPointcut"/> </aop:aspect> </aop:config> <bean id="advice" class="com.annotation.advice.OrderAdvice"></bean>
public class OrderAdvice { //增强方法,增强逻辑的具体的实现 public void adviceMethod() { System.out.println("this my advice method"); } }
不管目标方法是否正常结束,该增强都会被执行。该种增强可以用于资源释放
10.5 引介增强
<aop:config proxy-target-class="true"> <aop:pointcut id="myPointcut" expression="execution (* com.aspectj..*.query*(..)))"></aop:pointcut> <aop:aspect ref="advice"> <aop:declare-parents implement-interface="com.demo.inter.IUserDao" default-impl="com.demo.impl.UserDao" types-matching="com.annotation.dao.OrderDao+"/> </aop:aspect> </aop:config>
implement-interface:需要实现的接口
default-impl:接口默认实现类
types-matching:目标类,需要引介接口的目标类
11 绑定连接点信息
1.所有增强类型对应的方法第一个入参都可以声明为JoinPoint或者ProceedingJoinPoint
2.<aop:throwing>可以通过throwing属性绑定连接点对应的异常。<aop:returning>可以通过returning属性绑定连接点对应返回值
3.通过绑定切点函数的方式绑定连接点入参
<aop:config proxy-target-class="true"> <aop:pointcut id="myPointcut" expression="target(com.annotation.dao.OrderDao) and args(name,id)"></aop:pointcut> <aop:aspect ref="advice"> <aop:before method="adviceMethod" pointcut-ref="myPointcut"></aop:before> </aop:aspect> </aop:config> <bean id="advice" class="com.annotation.advice.OrderAdvice"></bean>
public class OrderAdvice { //增强方法,增强逻辑的具体的实现 public void adviceMethod(String name, int id) { System.out.println("this my advice method"); } }
将args函数的使用放在了配置文件中,使用的方法和之前介绍的一致,args中的命名必须要和增强方法入参的命名一致
12 Advisor配置
切面的配置和之前介绍的切面配置基本一致,切面是增强和切点的结合体。所以需要额外的声明增强,即实现Spring提供的增强接口
<aop:config proxy-target-class="true"> <aop:pointcut id="myPointcut" expression="execution (* com.aspectj..*.query*(..)))"></aop:pointcut> <aop:advisor advice-ref="advice" pointcut-ref="myPointcut"/>此处可以使用pointcut属性替换pointcut-ref </aop:config> <bean id="advice" class="com.annotation.advice.OrderAdvice"></bean>
//定义增强 public class OrderAdvice implements MethodBeforeAdvice{ //增强方法,增强逻辑的具体的实现 public void adviceMethod() { System.out.println("this my advice method"); }