Spring AOP(二)--注解方式

本文内容纲要:Spring AOP(二)--注解方式

本文介绍通过注解@AspectJ实现Spring AOP,这里要重点说明一下这种方式实现时所需的包,因为Aspect是第三方提供的,不包含在spring中,所以不能只导入spring-aop的包,为了安全起见我导入的包有(我是maven方式添加依赖):

Image

步骤如下:

一、创建连接点

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方法;

查看测试结果:

Image

从上面的运行结果来看,完全符合刚开始的流程图:

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

回到顶部