目录
1.2 在Spring配置文件中声明AspectJ的自动代理生成器
1.使用AOP通知注解的步骤
使用apsectj框架的注解,实现前置通知,步骤如下:
1.新建Maven项目2.修改pom.xml,加入依赖
spring-context依赖、spring-aspects依赖、junit3.创建业务接口和实现类
4.创建一个切面类(普通类)
1) 在类的上面加入@Aspect
2) 在类中定义方法,方法表示切面的功能。在方法的上面加入AspectJ框架中的通知注解
例如:@Before(value="切入点表达式")5.创建spring配置文件
1) 声明目标对象
2) 声明切面类对象
3) 声明自动代理生成器6.创建测试类,测试目标方法执行时,增加切面的功能
1.1 pom.xml文件中加入maven依赖
1. spring依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency>
2. spring-aspects依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
3.单元测试依赖 (可有可没有主要是测试用的)
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
1.2 在Spring配置文件中声明AspectJ的自动代理生成器
<?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: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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 声明自动代理生成器::目的是创建目标对象的代理(也就是将两个bean结合起来)
调用aspectj框架中的功能,寻找spring容器中的所有目标对象。把每个目标对象加入切面类中的功能
,生成代理,这个代理对象是修改内存中的目标对象,这个目标对象就是代理对象-->
<aop:aspectj-autoproxy />
</beans>
2.AOP中的5大通知注解
2.1 @Before:前置通知
/* 前置通知方法的定义 1.方法是public 2.方法是void 3.方法名称自定义 4.方法可以有参数,如果有是joinpoint 也可以没有 */ /** * @Aspect: 切面类的注解。位置:放到某个类的上面 。作用::表示当前类是切面类 * * @Before:前置通知 * 属性:value切入点表达式,表示切面的执行位置在这个方法时,会同时执行切面的功能。 * 位置:方法上面 * 特点:1.执行时间:在目标方法之前执行的, * 2.不会影响目标方法的执行 * 3.不会修改目标方法的执行结果。 * 切面类中的通知方法,可以有参数 * joinPoint必须是它。表示正在执行的业务方法。相当于反射中的Method * 使用要求:必须是参数列表的第一个。 * 作用:获取方法执行时的信息,例如方法名称,方法的参数集合 */
service包的接口类:
package com.liuhaiyang.service; public interface SomeService { void doSome(String name,Integer age); void doOther(); }
同级目录下Impl包实现类
package com.liuhaiyang.service.Impl; import com.liuhaiyang.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public void doSome(String name, Integer age) { System.out.println("业务方法dosome(),"+name+"创建商品订单,"+age); } @Override public void doOther() { System.out.println("doOther方法执行了============"); System.out.println("\r\n"); } }
通过前置通知方式完善功能
package com.liuhaiyang.handle; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import java.util.Date; /** *@Aspect: 切面类的注解。位置:放到某个类的上面 。作用::表示当前类是切面类 */ @Aspect //该注解表示这个类是切面类 public class MyAspect { /* 前置通知方法的定义 1.方法是public 2.方法是void 3.方法名称自定义 4.方法可以有参数,如果有是joinpoint 也可以没有 */ /** * @Before:前置通知 * 属性:value切入点表达式,表示切面的执行位置在这个方法时,会同时执行切面的功能。 * 位置:方法上面 * 特点:1.执行时间:在目标方法之前执行的, * 2.不会影响目标方法的执行 * 3.不会修改目标方法的执行结果。 */ // @Before(value ="execution(public void com.liuhaiyang.service.Impl.SomeServiceImpl.doSome(String ,Integer))") // @Before(value ="execution(* *..SomeServiceImpl.doSome(..))") //dosome方法的任意参数都可以加入切片功能 @Before(value ="execution(* *..SomeServiceImpl.do*(..))") //以do开头的所有方法的任意参数都可以加入切片功能 /* 可以在写一个@Before但是需要新写一个方法。且两个Before的先后顺不能确定 */ /** *切面类中的通知方法,可以有参数 * joinPoint必须是它。表示正在执行的业务方法。相当于反射中的Method * 使用要求:必须是参数列表的第一个。 * 作用:获取方法执行时的信息,例如方法名称,方法的参数集合 */ public void myBefore(JoinPoint jp){ //获取方法的定义 System.out.println("前置通知中,获取目标方法的定义:" +jp.getSignature()); //结果是:前置通知中,获取目标方法的定义:void com.liuhaiyang.service.SomeService.doOther() System.out.println("前置通知中,获取方法名称="+jp.getSignature().getName()); //获取方法执行时参数 Object[] obj=jp.getArgs(); int[] a={}; for(Object ob:obj) {System.out.println("参数有:"+ob);} String getname=jp.getSignature().getName(); if("doSome".equals(getname)){ //切面代码 System.out.println(getname+"前置通知,切面功能,在目标方法之前先执行:"+new Date()); } else if("doOther".equals(getname)){ System.out.println(getname+"前置通知,切面功能,在目标方法之前先执行:"+new Date()); } } }
在配置文件application.xml文件中添加对象和代理器的声明
<?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: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 https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 声明目标对象--> <bean id="someservice" class="com.liuhaiyang.service.Impl.SomeServiceImpl"/> <!-- 声明切面类对象--> <bean id="myaspect" class="com.liuhaiyang.handle.MyAspect"/> <!-- 声明自动代理生成器::目的是创建目标对象的代理(也就是将两个bean结合起来) 调用aspectj框架中的功能,寻找spring容器中的所有目标对象。把每个目标对象加入切面类中的功能 ,生成代理,这个代理对象是修改内存中的目标对象,这个目标对象就是代理对象--> <aop:aspectj-autoproxy /> </beans>
测试类
@Test public void test01(){ String conf="application.xml"; ApplicationContext ctx= new ClassPathXmlApplicationContext(conf); //加入代理的处理 //1.目标方法执行时,有切面功能。 // 2.service对象是改变后的代理对象com.sun.proxy.$Proxy10 SomeService someservice=(SomeService) ctx.getBean("someservice"); System.out.println(someservice.getClass().getName()); //这里的someservice是代理对象,不在是SomeService someservice.doSome("张三",22); }
结果:
看一下doOther是否能正常执行,执行结果是什么
测试类
@Test public void test02(){ String conf="application.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(conf); SomeService someservice=(SomeService) ctx.getBean("someservice"); someservice.doOther(); someservice.doSome("周爽",25); }
结果截图:
2.2 @AfterReturning:后置通知
/* 后置通知方法的定义 1.方法是public 2.方法是void 3.方法名称自定义 4.方法有参数,推荐使用Object类型 */ /** @AfterReturning:后置通知 * 属性:value切入点表达式 * returning自定义的变量,表示目标方法的返回值的 * 自定义变量名称必须和通知方法的形参名一样。 * 位置:在方法的上面 * 特点:1.在目标方法之后,执行的。 * 2.能获取到目标方法的执行结果。 * 3.不会影响目标方法的执行 *方法的参数: * Object res: 表示目标方法的返回值,使用res接受doOther的调用结果。 * Object res = doOther(); * * 后置通知的执行顺序 * Object res=someserviceImpl.doOther(..); * myAfterReturning(res); */
service接口
package com.liuhaiyang.service; import com.liuhaiyang.Stduent; public interface SomeService { void doSome(String name,Integer age); Stduent doOther(String name, Integer age); }
实现类:
package com.liuhaiyang.service.Impl; import com.liuhaiyang.Stduent; import com.liuhaiyang.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public void doSome(String name, Integer age) { System.out.println("\r\n"); System.out.println("业务方法dosome(),"+name+"创建商品订单,"+age); } @Override public Stduent doOther(String name, Integer age) { Stduent student=new Stduent(); System.out.println("执行业务方法doOther,"+name+"处理库存的"+age); student.setName(name); student.setAge(age); return student; } }
完善的功能
package com.liuhaiyang.handle; import com.liuhaiyang.Stduent; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; @Aspect //该注解表示这个类是切面类 public class MyAspect { @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))", returning="res") public void myAfterReturning(JoinPoint jp ,Object res){ // JoinPoint必须在Object前面 System.out.println(res.getClass().getName()); if(res!=null){ ((Stduent) res).setName("liuhaiyang"); ((Stduent) res).setAge(23); } System.out.println("后置通知,能拿到返回的结果+"+res); } }
配置文件(代码和上面的配置文件的差不多没写,写点核心的):
<!-- 声明目标对象--> <bean id="someservice" class="com.liuhaiyang.service.Impl.SomeServiceImpl"/> <!-- 声明切面类对象--> <bean id="myaspect" class="com.liuhaiyang.handle.MyAspect"/> <!-- 声明自动代理生成器::目的是创建目标对象的代理(也就是将两个bean结合起来) 调用aspectj框架中的功能,寻找spring容器中的所有目标对象。把每个目标对象加入切面类中的功能 ,生成代理,这个代理对象是修改内存中的目标对象,这个目标对象就是代理对象--> <aop:aspectj-autoproxy />
测试类:
@Test public void test01(){ String conf="application.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(conf); SomeService someservice=(SomeService) ctx.getBean("someservice"); Stduent res=someservice.doOther("瓦达",30); System.out.println(res); someservice.doSome("周爽",25); }
结果截图:
2.3 @Around:环绕通知
/* 环绕通知方法的定义 1.方法是public 2.方法必须有返回值, 推荐使用Object类型 3.方法名称自定义 4.方法必须有ProceedingJoinPoint参数; */ /** * @Around: 环绕通知 * 属性: value切入点表达式 * 位置:在方法定义的上面 * 返回值:Object,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值) * 参数: ProceedingJoinPoint,相当于反射中的Method。 * 作用:执行目标方法的,等于Maethod.invoke() * ProceedingJoinPoint继承了JoinPoint. * 特点:1.在目标方法前和后都能增强功能 * 2.控制目标方法是否执行。 * 3.修改目标方法的执行结果。 */
service接口:
package com.liuhaiyang.service; public interface SomeService { void doSome(String name,Integer age); String doOther(String name, Integer age); String doFirst(String name); }
实现类:
package com.liuhaiyang.service.Impl; import com.liuhaiyang.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public void doSome(String name, Integer age) { System.out.println("\r\n"); System.out.println("业务方法dosome(),"+name+"创建商品订单,"+age); } @Override public String doOther(String name, Integer age) { System.out.println("执行业务方法doOther,"+name+"处理库存的"+age); return "student"; } @Override public String doFirst(String name) { System.out.println(name+"执行了业务方法doFirst,处理库存"); return "abcd"; } }
通过环绕通知增强功能:
package com.liuhaiyang.handle; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import java.util.Date; @Aspect //该注解表示这个类是切面类 public class MyAspect { @Around("execution(* *..SomeServiceImpl.doFirst(..))") public Object myArroud(ProceedingJoinPoint pjp) throws Throwable { //获取方法执行时的参数值 String name=""; Object args[]= pjp.getArgs(); if (args!=null & args.length>0){ Object arg=args[0]; if (arg!=null){ name=(String) arg; } } for (Object arg:args){ //查看doFirst所有的参数 System.out.println(arg); } System.out.println("执行了环绕通知的myAroud方法,在目标的方法之前输出日志时间"+new Date()); Object str=null; if("小红".equals(name)){ //执行目标方法(doFirst) str=pjp.proceed();//pjp.proceed()相当于Maethod.invoke() 表示执行doFirst中的方法 } System.out.println("环绕通知之后,在你的目标方法之后,新增事务提交"); return str; //该处返回啥,就是啥。如果是str就是doFirst的返回参数,如果是”abc“,返回的就是abc } }
配置文件:
<!-- 声明目标对象--> <bean id="someservice" class="com.liuhaiyang.service.Impl.SomeServiceImpl"/> <!-- 声明切面类对象--> <bean id="myaspect" class="com.liuhaiyang.handle.MyAspect"/> <!-- 声明自动代理生成器:--> <aop:aspectj-autoproxy />
测试类:
package com.liuhaiyang; import com.liuhaiyang.service.Impl.SomeServiceImpl; import com.liuhaiyang.service.SomeService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class test01 { @Test public void test02(){ String conf="application.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(conf); SomeService someservice=(SomeService) ctx.getBean("someservice"); // String res=someservice.doOther("瓦达",30); // System.out.println(res); // someservice.doSome("周爽",25); String name=someservice.doFirst("小红"); System.out.println(name); } }
结果截图:
2.4 @AfterThrowing:异常通知
/* 异常通知方法的定义 1.方法是public 2.方法没有返回值,是void 3.方法名称自定义 4.方法有参数是Exception; */ /** * @AfterThrowing:异常通知 * 属性:value:切入点表达式 * throwing 自定义变量,表示目标方法抛出的异常。 变量名必须和通知方法的形参名一样 * 位置:在方法的上面 * 特点:1.在目标方法抛出异常后执行的,没有异常不执行 * 2.能获取到目标方法的异常信息 * 3.不是异常处理程序。可以的得到发生异常的通知,可以发送邮件,短信通知开发人员。 * 可以看作目标方法的监控程序。 * 相当于: * try{ * execution(* *..SomeServiceImpl.doSecond(..))" * } catch(Exception ex){ * System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因:"+ex.getMessage()); * } */
service接口:
package com.liuhaiyang.service; public interface SomeService { void doSecond(String name); }
实现类:
package com.liuhaiyang.service.Impl; import com.liuhaiyang.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public void doSecond(String name) { int a=10/0; System.out.println(name+"执行了业务方法doSecond"); } }
异常的增强功能:
package com.liuhaiyang.handle; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import java.util.Date; @Aspect //该注解表示这个类是切面类 public class MyAspect { @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex") public void myAfterThrowing(Exception ex){ System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因:"+ex.getMessage()); /* 异常发生可以做的一些事件: 记录异常发生时间,位置等信息。 发送邮件,短信通知开发人员 */ } }
配置文件同上。没啥好写的,以及写好几遍了
测试类:
@Test public void test02(){ String conf="application.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(conf); SomeService someservice=(SomeService) ctx.getBean("someservice"); someservice.doSecond("小路"); }
结果截图:
2.5 @After:最终通知
/** * @After:最终通知 * 属性:value切入点表达式 * 位置:在方法的上面 * 特点:1.在目标方法之后执行的。 * 2.总是会被执行。 * 3.可以用来做程序最后的收尾工作。例如清除临时数据,变量,清除内存 * 相当于: * try(){ * SomeServiceImpl.doThird(..) * }finally(){ * myAfter(); * } */
service接口:
package com.liuhaiyang.service; public interface SomeService { void doThird(String naem); }
实现类:
package com.liuhaiyang.service.Impl; import com.liuhaiyang.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public void doThird(String name) { System.out.println(name+"执行了业务方法doThird"); } }
最终通知增强功能:
package com.liuhaiyang.handle; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import java.util.Date; @Aspect //该注解表示这个类是切面类 public class MyAspect { @After("execution(* *..SomeServiceImpl.doThird(..))") public void myAfter(){ System.out.println("最终通知,本代码总是会被执行的!!"); //即使是出现异常,该代码也会被执行 } }
配置文件同上。
测试类:
@Test public void test03(){ String conf="application.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(conf); SomeService someservice=(SomeService) ctx.getBean("someservice"); someservice.doThird("梨花"); }
结果截图:
3. @Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
给增强功能的代码,其他代码和上面的大同小异。就不写了:
package com.liuhaiyang.handle; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import java.util.Date; @Aspect //该注解表示这个类是切面类 public class MyAspect { //前置通知 @Before("mypt()") public void myBefer(){ System.out.println("前置通知,在目标方法之前执行"); } //最终通知 @After("mypt()") public void myArroud() { System.out.println("最终通知,在方法的最后执行,无论是否出现异常"); } /** * @Pointcut:定义和管理切入点,不是通知注解。 可以写多个 * 属性:value 切入点表达式(可以省略) * 位置: 在一个自定义方法的上面,这个方法可以看做是切入点表达式的别名。 * 其他的通知注解中,可以使用方法名称,就表示使用这个切入点表达式 */ @Pointcut("execution(* *..SomeServiceImpl.doThird(..))") public void mypt(){ //无需代码 } }