Spring框架学习

文章40 |   阅读 21319 |   点赞0

来源:https://blog.csdn.net/yerenyuan_pku/category_6457009.html

Spring入门第七讲——Spring AOP的注解开发

x33g5p2x  于2021-12-18 转载在 其他  
字(12.9k)|赞(0)|评价(0)|浏览(557)

在前一讲中,我已讲过Spring使用基于AspectJ的XML配置文件的方式进行AOP开发,现在我就来讲讲怎样使用基于AspectJ的注解的方式进行AOP开发。

使用基于AspectJ的注解的方式进行AOP开发

创建web项目,引入jar包

首先创建一个动态web项目,例如spring_demo03_aop,然后导入Spring框架相关依赖jar包,要导入哪些jar包呢?这里不废话,直接给出要导入的jar包。

引入相关的配置文件

首先,引入Spring的配置文件,在该文件中应引入aop约束,这样一开始applicationContext.xml文件的内容就应该是下面的样子。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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 http://www.springframework.org/schema/aop/spring-aop.xsd">
  3. </beans>

然后,还要记得在src目录下引入Log4j的配置文件(log4j.properties),也就是日志记录文件,该文件内容如下:

  1. ### direct log messages to stdout ###
  2. log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  3. log4j.appender.stdout.Target=System.err
  4. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  5. log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
  6. ### direct messages to file mylog.log ###
  7. log4j.appender.file=org.apache.log4j.FileAppender
  8. log4j.appender.file.File=c\:mylog.log
  9. log4j.appender.file.layout=org.apache.log4j.PatternLayout
  10. log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
  11. ### set log levels - for more verbose logging change 'info' to 'debug' ###
  12. # error warn info debug trace
  13. log4j.rootLogger= info, stdout

编写目标类并配置

首先,在src目录下创建一个com.meimeixia.spring.demo03包,并在该包下创建一个名为OrderDao的类。

  1. package com.meimeixia.spring.demo01;
  2. public class OrderDao {
  3. public void save() {
  4. System.out.println("保存订单...");
  5. }
  6. public void update() {
  7. System.out.println("修改订单...");
  8. }
  9. public void delete() {
  10. System.out.println("删除订单...");
  11. }
  12. public void find() {
  13. System.out.println("查询订单...");
  14. }
  15. }

然后,在Spring配置文件中对以上目标类进行配置。

编写切面类并配置

现在有这样一个需求:我们想在OrderDao类的save方法执行之前,就简简单单地执行一个操作,那该咋怎呢?这时我们可以编写一个切面类,并在切面类中随便编写一个方法,例如下面的before方法。待会,我们就使用注解对该切面类进行增强,让切面类中的before方法在目标类的save方法执行之前执行。

  1. package com.meimeixia.spring.demo01;
  2. public class MyAspectAnno {
  3. public void before() {
  4. System.out.println("前置增强--------------------");
  5. }
  6. }

接着,在Spring配置文件中对以上切面类进行配置。

使用注解对目标类进行增强

首先,我们需要在Spring的配置文件中开启注解的AOP开发。

然后,我们就可以在切面类上使用注解了。

  1. package com.meimeixia.spring.demo01;
  2. import org.aspectj.lang.annotation.Aspect;
  3. import org.aspectj.lang.annotation.Before;
  4. /** * 切面类(注解的切面类) * @author liayun * */
  5. @Aspect //Spring会把这个类识别成一个切面
  6. public class MyAspectAnno {
  7. //@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
  8. @Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
  9. public void before() {
  10. System.out.println("前置增强--------------------");
  11. }
  12. }

温馨提示:@Aspect注解用于定义切面类,@Before注解代表的是前置通知。

编写测试类并进行测试

首先,在com.meimeixia.spring.demo01包下创建一个SpringDemo01的单元测试类,其内容如下,可以看出我们是通过Spring来整合JUnit进行单元测试的。

  1. package com.meimeixia.spring.demo01;
  2. import javax.annotation.Resource;
  3. import org.junit.Test;
  4. import org.junit.runner.RunWith;
  5. import org.springframework.test.context.ContextConfiguration;
  6. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  7. /** * Spring的AOP的注解开发 * @author liayun * */
  8. @RunWith(SpringJUnit4ClassRunner.class)
  9. @ContextConfiguration("classpath:applicationContext.xml")
  10. public class SpringDemo01 {
  11. @Resource(name="orderDao")
  12. private OrderDao orderDao;
  13. @Test
  14. public void demo01() {
  15. orderDao.save();//作断点调试,可以看到使用到的是Cglib的动态代理:CglibAopProxy
  16. orderDao.update();
  17. orderDao.delete();
  18. orderDao.find();
  19. }
  20. }

然后,运行以上demo01单元测试方法,Eclipse控制台就会打印出如下内容。

Spring AOP注解开发中的通知类型

Spring AOP注解开发入门之后,咱就来看一下AOP注解开发中的通知类型。

前置通知

前置通知是指在目标方法执行之前进行操作。上面我演示的就是前置通知,这里不再赘述。

后置通知

后置通知是指在目标方法执行之后进行操作。它除了可以获得切入点的信息以外,还可以获得方法的返回值。为了验证这一点,首先,将OrderDao类修改成下面这个样子(主要是修改了一下delete方法,让其返回一个字符串)。

  1. package com.meimeixia.spring.demo01;
  2. public class OrderDao {
  3. public void save() {
  4. System.out.println("保存订单...");
  5. }
  6. public void update() {
  7. System.out.println("修改订单...");
  8. }
  9. public String delete() {
  10. System.out.println("删除订单...");
  11. return "删除订单成功";
  12. }
  13. public void find() {
  14. System.out.println("查询订单...");
  15. }
  16. }

然后,在MyAspectAnno切面类中添加一个afterReturning方法,并在该方法上使用@AfterReturning注解,告诉Spring要在OrderDao类中的delete方法上应用后置通知。

  1. package com.meimeixia.spring.demo01;
  2. import org.aspectj.lang.annotation.AfterReturning;
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.aspectj.lang.annotation.Before;
  5. /** * 切面类(注解的切面类) * @author liayun * */
  6. @Aspect //Spring会把这个类识别成一个切面
  7. public class MyAspectAnno {
  8. //@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
  9. @Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
  10. // @Before(value="MyAspectAnno.pointcut2()")
  11. public void before() {
  12. System.out.println("前置增强--------------------");
  13. }
  14. //后置通知
  15. @AfterReturning(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))", returning="result")
  16. public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
  17. System.out.println("后置增强--------------------" + result);
  18. }
  19. }

最后,运行SpringDemo01单元测试类中的demo01方法,你就会看到Eclipse控制台打印出了如下内容。

环绕通知

环绕通知是功能最强的一个通知,它是指在目标方法执行之前和之后进行操作。很重要的一点就是它可以阻止目标方法的执行。为了验证这一点,我们可以在MyAspectAnno切面类中添加一个如下的around方法,并在该方法上使用@Around注解,告诉Spring要在OrderDao类中的update方法上应用环绕通知。

  1. package com.meimeixia.spring.demo01;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.AfterReturning;
  4. import org.aspectj.lang.annotation.Around;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.aspectj.lang.annotation.Before;
  7. /** * 切面类(注解的切面类) * @author liayun * */
  8. @Aspect //Spring会把这个类识别成一个切面
  9. public class MyAspectAnno {
  10. //@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
  11. @Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
  12. public void before() {
  13. System.out.println("前置增强--------------------");
  14. }
  15. //后置通知
  16. @AfterReturning(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))", returning="result")
  17. public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
  18. System.out.println("后置增强--------------------" + result);
  19. }
  20. //环绕通知
  21. @Around(value="execution(* com.meimeixia.spring.demo01.OrderDao.update(..))")
  22. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  23. System.out.println("环绕前增强--------------------");
  24. Object obj = joinPoint.proceed();//执行我们的目标方法,它会返回一个Object
  25. System.out.println("环绕后增强--------------------");
  26. return obj;
  27. }
  28. }

接着,运行SpringDemo01单元测试类中的demo01方法,你就会看到Eclipse控制台打印出了如下内容。

异常抛出通知

异常抛出通知是指在程序出现异常的时候而进行的操作,而且它还可以获得异常的信息。异常抛出通知能想到的一个应用场景就是在事务管理的时候会用到。为了验证这一点,首先,修改一下OrderDao类中的find方法,使其抛出一个除零异常。

  1. package com.meimeixia.spring.demo01;
  2. public class OrderDao {
  3. public void save() {
  4. System.out.println("保存订单...");
  5. }
  6. public void update() {
  7. System.out.println("修改订单...");
  8. }
  9. public String delete() {
  10. System.out.println("删除订单...");
  11. return "删除订单成功";
  12. }
  13. public void find() {
  14. System.out.println("查询订单...");
  15. int i = 1 / 0;
  16. }
  17. }

然后,在MyAspectAnno切面类中添加如下一个afterThrowing方法,并在该方法上使用@AfterThrowing注解,告诉Spring要在OrderDao类中的find方法上应用异常抛出通知。

  1. package com.meimeixia.spring.demo01;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.AfterReturning;
  4. import org.aspectj.lang.annotation.AfterThrowing;
  5. import org.aspectj.lang.annotation.Around;
  6. import org.aspectj.lang.annotation.Aspect;
  7. import org.aspectj.lang.annotation.Before;
  8. /** * 切面类(注解的切面类) * @author liayun * */
  9. @Aspect //Spring会把这个类识别成一个切面
  10. public class MyAspectAnno {
  11. //@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
  12. @Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
  13. public void before() {
  14. System.out.println("前置增强--------------------");
  15. }
  16. //后置通知
  17. @AfterReturning(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))", returning="result")
  18. public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
  19. System.out.println("后置增强--------------------" + result);
  20. }
  21. //环绕通知
  22. @Around(value="execution(* com.meimeixia.spring.demo01.OrderDao.update(..))")
  23. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  24. System.out.println("环绕前增强--------------------");
  25. Object obj = joinPoint.proceed();//执行我们的目标方法,它会返回一个Object
  26. System.out.println("环绕后增强--------------------");
  27. return obj;
  28. }
  29. //异常抛出通知
  30. @AfterThrowing(value="execution(* com.meimeixia.spring.demo01.OrderDao.find(..))", throwing="e")
  31. public void afterThrowing(Throwable e) {//这儿的e一定得跟throwing="e"中的e对应上
  32. System.out.println("异常抛出增强--------------------" + e.getMessage());
  33. }
  34. }

最后,运行SpringDemo01单元测试类中的demo01方法,你就会看到Eclipse控制台打印出了如下内容。

最终通知

无论目标方法是否出现异常,最终通知都会执行。此时,我们已经知道了OrderDao类中的find方法(也即目标方法)抛出了一个除零异常。现在,咱就是要看看find方法(也即目标方法)出现了异常,最终通知会不会执行。为了验证这一点,我们在MyAspectAnno切面类中添加如下的一个after方法,并在该方法上使用@After注解,告诉Spring要在OrderDao类中的find方法上应用最终通知。

  1. package com.meimeixia.spring.demo01;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.After;
  4. import org.aspectj.lang.annotation.AfterReturning;
  5. import org.aspectj.lang.annotation.AfterThrowing;
  6. import org.aspectj.lang.annotation.Around;
  7. import org.aspectj.lang.annotation.Aspect;
  8. import org.aspectj.lang.annotation.Before;
  9. /** * 切面类(注解的切面类) * @author liayun * */
  10. @Aspect //Spring会把这个类识别成一个切面
  11. public class MyAspectAnno {
  12. //@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
  13. @Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
  14. public void before() {
  15. System.out.println("前置增强--------------------");
  16. }
  17. //后置通知
  18. @AfterReturning(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))", returning="result")
  19. public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
  20. System.out.println("后置增强--------------------" + result);
  21. }
  22. //环绕通知
  23. @Around(value="execution(* com.meimeixia.spring.demo01.OrderDao.update(..))")
  24. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  25. System.out.println("环绕前增强--------------------");
  26. Object obj = joinPoint.proceed();//执行我们的目标方法,它会返回一个Object
  27. System.out.println("环绕后增强--------------------");
  28. return obj;
  29. }
  30. //异常抛出通知
  31. @AfterThrowing(value="execution(* com.meimeixia.spring.demo01.OrderDao.find(..))", throwing="e")
  32. public void afterThrowing(Throwable e) {//这儿的e一定得跟throwing="e"中的e对应上
  33. System.out.println("异常抛出增强--------------------" + e.getMessage());
  34. }
  35. //最终通知
  36. @After(value="execution(* com.meimeixia.spring.demo01.OrderDao.find(..))")
  37. public void after() {
  38. System.out.println("最终增强--------------------");
  39. }
  40. }

接着,运行SpringDemo01单元测试类中的demo01方法,你就会看到Eclipse控制台打印出了如下内容。

从上面输出的结果中,我们就证明了即使目标方法出现了异常,最终通知也会执行。

Spring AOP注解开发中的切入点的配置

假设切面类里面有很多种通知,但它们都需要作用于目标类的同一个方法上,比如说find方法,假如说我有一天不想作用在find方法上了,而想作用在save方法或者update方法上,那该咋办呢?这时,我们得到切面类里面一个一个改,如果切面类这里面的通知很多,那么我们是不是得改好几个啊!这势必会很麻烦!为了解决这种问题,咱可以使用@Pointcut注解来定义切入点。例如,咱可以在MyAspectAnno切面类里面定义如下一些切入点。

那么,我们怎么把之前写好的各种类型的通知应用在这些切入点上呢?也就是说MyAspectAnno切面类定义了以上一系列切入点之后,可以修改成下面这个样子。

  1. package com.meimeixia.spring.demo01;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.After;
  4. import org.aspectj.lang.annotation.AfterReturning;
  5. import org.aspectj.lang.annotation.AfterThrowing;
  6. import org.aspectj.lang.annotation.Around;
  7. import org.aspectj.lang.annotation.Aspect;
  8. import org.aspectj.lang.annotation.Before;
  9. import org.aspectj.lang.annotation.Pointcut;
  10. /** * 切面类(注解的切面类) * @author liayun * */
  11. @Aspect //Spring会把这个类识别成一个切面
  12. public class MyAspectAnno {
  13. //@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
  14. @Before(value="MyAspectAnno.pointcut2()")
  15. public void before() {
  16. System.out.println("前置增强--------------------");
  17. }
  18. //后置通知
  19. @AfterReturning(value="MyAspectAnno.pointcut4()", returning="result")
  20. public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
  21. System.out.println("后置增强--------------------" + result);
  22. }
  23. //环绕通知
  24. @Around(value="MyAspectAnno.pointcut3()")
  25. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  26. System.out.println("环绕前增强--------------------");
  27. Object obj = joinPoint.proceed();//执行我们的目标方法,它会返回一个Object
  28. System.out.println("环绕后增强--------------------");
  29. return obj;
  30. }
  31. //异常抛出通知
  32. @AfterThrowing(value="MyAspectAnno.pointcut1()", throwing="e")
  33. public void afterThrowing(Throwable e) {//这儿的e一定得跟throwing="e"中的e对应上
  34. System.out.println("异常抛出增强--------------------" + e.getMessage());
  35. }
  36. //最终通知
  37. @After(value="MyAspectAnno.pointcut1()")
  38. public void after() {
  39. System.out.println("最终增强--------------------");
  40. }
  41. /* * 这里面有一个问题:假设我这里面有很多种通知,但都需要作用于同一个方法上,比如说find()方法,假如说我有一天不想作用在find()方法上了, * 我想作用在save()方法或者update()方法上,咋办呢?你是不是得到这边一个一个改啊!那如果我这里面的通知很多,但都 * 作用于同一个方法上,你是不是得改好几个啊?很麻烦! * */
  42. //切入点的注解
  43. @Pointcut(value="execution(* com.meimeixia.spring.demo01.OrderDao.find(..))")
  44. private void pointcut1() {}//该方法没有特殊的意义,私有就行
  45. @Pointcut(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
  46. private void pointcut2() {}
  47. @Pointcut(value="execution(* com.meimeixia.spring.demo01.OrderDao.update(..))")
  48. private void pointcut3() {}
  49. @Pointcut(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))")
  50. private void pointcut4() {}
  51. }

相关文章