Spring AOP(二)--注解方式
本文内容纲要:Spring AOP(二)--注解方式
本文介绍通过注解@AspectJ实现Spring AOP,这里要重点说明一下这种方式实现时所需的包,因为Aspect是第三方提供的,不包含在spring中,所以不能只导入spring-aop的包,为了安全起见我导入的包有(我是maven方式添加依赖):
步骤如下:
一、创建连接点
spring是方法级别的拦截器,所以连接点就是某个类中的某个方法,从动态代理的角度来看就是将要拦截的方法织入AOP通知。
1⃣️创建一个接口
1 public interface EmployeeService {2
3 public void getEmployeeInfo(Employee employee);
4
5 public void getEmployeeSex(Employee employee);
6 }
这个接口中提供了两个方法,后续会用来测试连接点的概念,因为只有将方法织入到AOP才会执行完整的拦截流程。
2⃣️创建接口实现类,增加注解@Component
1 @Component 2 public class EmployeeServiceImpl implements EmployeeService {
3
4 @Override
5 public void getEmployeeInfo(Employee employee) {
6 System.out.println("name:" + employee.getUsername() + ";sex:" + employee.getSex());
7 }
8
9 @Override
10 public void getEmployeeSex(Employee employee) {
11 System.out.println("性别:"+employee.getSex());
12 }
13 }
二、创建切面
创建好了连接点之后就可以创建切面了,它就相当于是一个拦截器,在spring中只要使用@Aspect注解一个类,spring ioc容器就会将它视为一个切面处理。
1 package com.hyc.aop.aspect; 2
3 import org.aspectj.lang.ProceedingJoinPoint;
4 import org.aspectj.lang.annotation.After;
5 import org.aspectj.lang.annotation.AfterReturning;
6 import org.aspectj.lang.annotation.AfterThrowing;
7 import org.aspectj.lang.annotation.Around;
8 import org.aspectj.lang.annotation.Aspect;
9 import org.aspectj.lang.annotation.Before;
10 import org.aspectj.lang.annotation.DeclareParents;
11 import org.aspectj.lang.annotation.Pointcut;
12
13 import com.hyc.pojo.Employee;
14
15 /**
16 * 定义一个切面
17 *
18 * @Aspect 该注解表示这个类就是一个切面了
19 */
20 @Aspect
21 public class EmployeeAspect {
22
29 /**
30 * 定义一个切点,通知aop什么时候启动拦截并织入对应流程
31 * 注意以下几点:
32 * 1、方法返回类型* 和方法之间有空格
33 * 2、在下面的四个方法中引用这个切点时方法名要加括号
34 * 3、execution正则表达式中的方法就是一个连接点,将代理对象和切面相连,如果不定义这个连接点,则不会将代理对象的方法和切面相连
35 */
36 @Pointcut("execution(* com.hyc.aop.aspect.EmployeeServiceImpl.getEmployeeInfo(..))")
37 public void getInfo() {
38
39 }
40
41 @Before("getInfo()")
42 public void before() {
43 System.out.println("before:代理方法执行之前");
44 }
45
46 @After("getInfo()")
47 public void after() {
48 System.out.println("after:代理方法执行完毕");
49 }
50
51 @AfterReturning("getInfo()")
52 public void afterReturning() {
53 System.out.println("afterReturning:代理方法执行完毕,执行成功");
54 }
55
56 @AfterThrowing("getInfo()")
57 public void afterThrowing() {
58 System.out.println("afterThrowing:代理方法执行完毕,执行过程出现异常");
59 }
60 }
上面的代码中红色加粗部分所代表的意思如下:
- @Aspect注解:告诉spring,这个类是一个切面;
- @Pointcut注解:定义一个切点,并告诉AOP什么时候启动拦截并织入对应流程;
- @before、@after、@afterReturning、@afterThrowing分别是四种通知,它们可以引用之前定义的切点,也可以有自己的切点;
有必要解释一下定义切点注解@Pointcut中的内容:
1 @Pointcut("execution(* com.hyc.aop.aspect.EmployeeServiceImpl.getEmployeeInfo(..))")
在上面的注解中,定义了execution的正则表达式,spring是通过这个正则表达式判断是否需要拦截你所定义的方法,即被代理对象的方法。
- execution:代表执行方法的时候会触发;
- *:代表方法的返回类型任意;
- com.hyc.aop.aspect.EmployeeServiceImpl:被代理类的全限定名,注意它和前面的返回类型*之间有一个空格;
- getEmployeeInfo:被拦截方法名称;
- (..):方法中的参数,类型任意;
通过上面的描述,上述注解及内部正则表达式的意思就是:全限定名为com.hyc.aop.aspect.EmployeeServiceImpl的类中的getEmployeeInfo方法被当做一个切点,当程序执行这个方法的时候对它进行拦截,这样就能按照AOP通知的规则把这个方法织入流程中。
三、创建配置类,采用注解java配置
1 /* 2 * 定义一个配置类,通过java配置的方式获取切面
3 */
4 @Configuration
5 @ComponentScan(basePackages= {"com.hyc.aop.aspect","com.hyc.pojo"})
6 @EnableAspectJAutoProxy //自动代理,代替了动态代理的实现
7 public class AspectConfig {
8 //返回一个切面
9 @Bean
10 public EmployeeAspect getAspect() {
11 return new EmployeeAspect();
12 }
13
14 }
这个配置就是之前介绍过的注解方式装配bean的配置方法,不过对AOP有效的注解是@EnableAspectJAutoProxy,从字面意思理解它是开启了切面自动代理功能,其实就是启用了AspectJ框架的自动代理,这样spring就会生成一个代理对象,进而使用AOP,其中的getAspect方法是生成了一个切面。
四、测试
完成了上面的配置之后,我就可以进行测试了,测试方法如下:
1 @Test 2 public void testAopByConfig() {
3 @SuppressWarnings("resource")
4 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspectConfig.class);
5 EmployeeService es = (EmployeeService) context.getBean(EmployeeService.class);
6 Employee employee = (Employee) context.getBean("employee");
7 es.getEmployeeInfo(employee);
8 employee = null;
9 es.getEmployeeInfo(employee);
10 }
上面的单元测试方法中红色部分:
第四行:首先是获取配置类的上下文,此时已经启动AspectJ的自动代理,并生成了一个切面;
第五行:通过上下文就能生成一个代理对象,如果被代理类有接口则采用jdk动态代理,否则就是CGLIB动态代理;
第七行:测试方法,因为employee不为空,所以可以正常返回,并执行afterReturning方法;
第八、九行:将employee设置为null,执行过程出现异常,所以会执行aferThrowing方法;
查看测试结果:
从上面的运行结果来看,完全符合刚开始的流程图:
1、首先执行before方法;
2、执行被代理对象的方法;
3、执行完被代理对象方法后,不管成功与否都会执行after方法;
4、如果被代理对象的方法正常返回,则执行afterReturning方法,如果返回异常,则执行afterTrowing方法(结果中的afterException是书写错误,其实都一个意思啦????)
五、环绕通知
环绕通知是spring aop中最强大的功能,它可以同时实现前置通知和后置通知,并且保留了调度被代理对象原有方法的功能,在之前的切面类中加入以下环绕通知:
1 @Around("getInfo()") 2 public void around(ProceedingJoinPoint pjp) {
3 System.out.println("around before...");
4 try {
5 pjp.proceed();
6 } catch (Throwable e) {
7 //此处将异常打印出来
8 System.out.println(e.getLocalizedMessage());
9 }
10 System.out.println("around after...");
11 }
在切面中通过@Around注解加入了切面环绕通知,这个通知里有一个参数ProceedingJoinPoint,这个参数是spring提供的,使用它可以反射连接点方法。
下面来看一下加入环绕通知之后的执行结果:
1 around before... 2 before:代理方法执行之前
3 name:张三;sex:男
4 around after...
5 after:代理方法执行完毕
6 afterReturning:代理方法执行完毕,执行成功
7 ----分割线----
8 around before...
9 before:代理方法执行之前
10 null
11 around after...
12 after:代理方法执行完毕
13 afterReturning:代理方法执行完毕,执行成功
可以看到执行的顺序,注意⚠️,使用这种方式时around before在before之前执行,但是XML方式它在before方法执行,这个下一篇文文章验证。
但是使用环绕方法的时候,因为执行被代理方法时空指针异常被捕获了,所以当我在测试代码中把employee设置为null时还是会执行afterReturning方法。
六、给通知传参数
有时候我们希望能给某个通知传递一些参数,当然这些参数就是通过连接点方法传进去的,比如之前的连接点方法getEmployeeInfo(Employee employee);它里面有一个参数,现在我想把这个参数只传递给前置通知before,那么此时before的切点就不能引用公共方法,而是重写自己的,并传入参数,如下代码所示:
1 @Before("execution(* com.hyc.aop.aspect.EmployeeServiceImpl.getEmployeeInfo(..)) && args(employee)")2 public void before(Employee employee) {
3 System.out.println("before:代理方法执行之前,username:" + employee.getUsername());
4 }
上面代码中黄色背景部分就是传递参数的方式,注意它要写在execution()的外面
下面来看测试结果:
1 around before... 2 before:代理方法执行之前,username:张三
3 name:张三;sex:男
4 around after...
5 after:代理方法执行完毕
6 afterReturning:代理方法执行完毕,执行成功
7 ----分割线----
8 around before...
9 null
10 around after...
11 after:代理方法执行完毕
12 afterReturning:代理方法执行完毕,执行成功
从结果可以看出,当参数不为空时,可以正常传递并执行before通知,但是如果参数为空,则不会执行before.
其实这种方式还能引申出另一个问题,每个同通知是否可以配置不同的连接点呢?比如我的before通知和其他通知定义不同的连接点,之前在定义连接点类的时候还定义了一个getEmployeeSex(Employee employee)方法,现在正是用它的时候,我现在把它作为before通知的连接点,所以before的配置改为如下:
1 @Before("execution(* com.hyc.aop.aspect.EmployeeServiceImpl.getEmployeeSex(..)) && args(employee)")2 public void before(Employee employee) {
3 System.out.println("before:代理方法执行之前,username:" + employee.getUsername());
4 }
其他配置不变,测试代码改为:
1 @Test 2 public void testAopByConfig() {
3 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspectConfig.class);
4 // 要获取真实对象bean,必须要在类上使用Componetn注解
5 EmployeeService es = (EmployeeService) context.getBean(EmployeeService.class);
6 Employee employee = (Employee) context.getBean("employee");
7 es.getEmployeeInfo(employee);
8 es.getEmployeeSex(employee);
9 System.out.println("----分割线----");
10 employee = null;
11 es.getEmployeeInfo(employee);
12 }
增加对方法getEmployeeSex的调用,测试结果如下:
1 around before... 2 name:张三;sex:男
3 around after...
4 after:代理方法执行完毕
5 afterReturning:代理方法执行完毕,执行成功
6 before:代理方法执行之前,username:张三
7 性别:男
8 ----分割线----
9 around before...
10 null
11 around after...
12 after:代理方法执行完毕
13 afterReturning:代理方法执行完毕,执行成功
从上面的结果可以看出,执行第一个连接点方法时,没有执行before通知,执行第二个连接点方法时,只执行了before通知;由此可见我们可以为不同的通知创建不同的连接点方法。比如在一个包含数据库的业务逻辑中,在before中进行数据库连接处理,在after中进行数据库连接关闭等等。
七、引入
spring aop只是通过动态代理的方式把不同的类织入到它所约定的流程中,有时候我们也希望通过引入一些新的类来完善被织入的类,比如上面的连接点中,获取属性值之前没有判断employee对象是否为空,现在有一个类进行了对象是否为空的判断,我想用它来完善之前的代码,可是怎么能使用aop引用它呢?要知后事如何,且看下面分解:
第一步:新建一个接口和实现类,完成是否为空的判断逻辑
1⃣️接口
1 public interface EmployeeCheckService {2 public boolean isPass(Employee employee);
3 }
2⃣️实现类
1 public class EmployeeCheckServiceImpl implements EmployeeCheckService {2
3 @Override
4 public boolean isPass(Employee employee) {
5 return employee != null;
6 }
7
8 }
第二步:在切面中增加一个接口类的属性,并使用注解@DeclaredParents
1 /**2 * 定义一个EmployeeCheckService类的成员变量作为引入对象
3 */
4 @DeclareParents(value = "com.hyc.aop.aspect.EmployeeServiceImpl+", defaultImpl = EmployeeCheckServiceImpl.class)
5 public EmployeeCheckService employeeCheckService;
上面h红色部分的注解有两个属性:
- value="com.hyc.aop.aspect.EmployeeServiceImpl+" 表示对类EmployeeServiceImpl进行增强,有了这个定义之后,就能将EmployeeServiceImpl类强制转化成要引入的类型了,可以理解为让EmployeeServiceImpl实现了EmployeeCheckService接口,毕竟看这个注解的名称就是让属性成为注解值的接口嘛!
- defaultImpl代表它默认的实现类
有了这个配置之后,可以将测试类修改成下面这样:
1 @Test 2 public void testAopByConfig() {
3 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspectConfig.class);
4 // 要获取真实对象bean,必须要在类上使用Componetn注解
5 EmployeeService es = (EmployeeService) context.getBean(EmployeeService.class);
6 Employee employee = (Employee) context.getBean("employee");
7 EmployeeCheckService ecs = (EmployeeCheckService) es;
8 if (ecs.isPass(employee)) {
9 es.getEmployeeInfo(employee);
10 es.getEmployeeSex(employee);
11 }
12 employee = null;
13 if (ecs.isPass(employee)) {
14 es.getEmployeeInfo(employee);
15 es.getEmployeeSex(employee);
16 }
17 }
看上面代码中的加粗红色部分:
第七行:将被代理对象强制转化成要引入的类型,这样就能调用它的方法了,因为此时已经将这个类引入到AOP中了。查看测试结果:
1 around before...2 before:代理方法执行之前
3 name:张三;sex:男
4 around after...
5 after:代理方法执行完毕
6 afterReturning:代理方法执行完毕,执行成功
7 性别:男
可以看到到对象employee为空时,直接返回,没有进入到方法执行,所以引入成功。
本文内容总结:Spring AOP(二)--注解方式
原文链接:https://www.cnblogs.com/hellowhy/p/9721251.html
以上是 Spring AOP(二)--注解方式 的全部内容, 来源链接: utcz.com/z/296927.html