欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

荐 微服务之Spring Boot2—降低开发复杂度之面向切面AOP

程序员文章站 2022-12-20 15:34:08
微服务之Spring Boot2—降低开发复杂度之面向切面AOPSpring Boot中常用注解说明@SpringBootApplication@Configuration@Bean@ComponentScan在STS项目pom.xml中添加aop的starter前置通知后置通知后置异常通知后置最终通知环绕通知AOP总结在软件行业,AOP为Aspect Oriented Programming的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是...


在软件行业,AOP为Aspect Oriented Programming的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高了程序的可重用性,同时提高了开发的效率。
Spring AOP的原理如下图所示:

荐
                                                        微服务之Spring Boot2—降低开发复杂度之面向切面AOP
AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,它们经常发生在核心关注点的多处,而各处都基本相似,比如权限认证、日志、事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP中的一些名词如下。

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是JAVAEE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。
  • 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
  • 通知(Advice):在切面的某个特定的连接点上执行的动作,其中包括“around”、“before”,和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都以拦截器作为通知模型,并维护一个以连接点为中心的拦截器链。
  • 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切入点语法进行匹配。
  • 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,可以使用引入来使一个Bean实现IsModified接口,以便简化缓存机制。
  • 目标对象(Target Object):被一个或者多个切面所通知的对象,也被称为被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)对象。
  • AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如:通知方法执行,等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
  • 织入(Weaving):把切面连接到其他应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如,使用AspectJ编译器)、类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

Spring Boot中常用注解说明

@SpringBootApplication

@SpringBootApplication是spring boot最重要的一个注解,用于快捷配置启动类。相当于@Configuration+@EnableAutoConfiguration+@ComponentScan的组合。

@Configuration

@Configuration的注解类标识这个类可以使用Spring IOC容器作为Bean定义的来源。

@Bean

@Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册为在Spring应用程序上下问中的Bean。
@EnableAutoConfiguration:能够自动配置Spring的上下文,试图猜测和配置你想要的Bean类,通常会自动根据类路径和Bean定义自动配置。

@Component、@Service、@Repository、@Controller、@RestController

这几个注解都是用于类的前面

  • @Component 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注(把普通pojo实例化到spring容器中)
  • @Service 用于标注业务层组件
  • @Repository 用于标注数据访问层组件
  • @Controller 用于标注控制层组件
  • @RestController 用于标注控制层组件,相当于@Controller+@ResponseBody的组合

@RequestMapping、@ResponseBody

  • @RequestMapping 用于在控制层对一个方法进行访问路径的映射,标注在方法前
  • @ResponseBody 用于在控制层对一个方法的返回值标注为一个Entity对象,标注在方法前

@ComponentScan

@ComponentScan会自动扫描指定包下的全部标有@Component的类,并注册成Bean,当然包括@Component下的子注解@Service、@Repository和@Controller。

@Aspect、@Pointcut、@Before、@AfterReturning、@AfterThrowing、@After、@Around

  • @Aspect 描述一个切面类,定义切面类时需要打上这个注解,用于对类进行标注。
  • @Pointcut 描述一个切入点,用于对方法进行标注。
  • @Before 前置通知,用于对方法进行标注。
  • @AfterReturning 后置返回通知,用于对方法进行标注。
  • @AfterThrowing 后置异常通知,用于对方法进行标注。
  • @After 后置最终通知,用于对方法进行标注。
  • @Around 环绕通知,用于对方法进行标注。

@SpringBootTest

@SpringBootTest 用于标注一个Spring Boot的单元测试类,其中参数

  • classes = 主类.class
  • webEnvironment = WebEnvironment.RANDOM_PORT

在STS中新建一个Spring Boot项目

在STS中新建一个Sping Boot项目,详细参考我之前的一篇博文:
微服务之Sprint Boot2—一切从简单开始

在Spring Boot项目pom.xml中添加aop的starter

在pom.xml中增加spring-boot-starter-aop,如下:

<!-- 增加spring-aop支持  -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在Spring Boot项目中使用Log4j2

日志框架的性能比较:Log4j<Logback<Log4j2,所以我们在项目中直接整合Log4j2。
在pom.xml文件中添加log4j2的依赖,如下:

<!-- log related -->
<!-- exclude掉spring-boot的默认log配置  -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<!-- 引入Log4j2依赖  -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- 加上这个才能识别到log4j2.yml文件  -->
<dependency>
	<groupId>com.fasterxml.jackson.dataformat</groupId>
	<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<!-- end of log related -->

前置通知

在项目中新建3个包:

  • com.wongoing.aop:用于放AOP的通知类
  • com.wongoing.service:用于放业务类
  • com.wongoing.controller:用于放控制器类
    1、在com.wongoing.aop包下创建通知类MyAopAdvice.java
    代码如下:
package com.wongoing.aop;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAopAdvice {
	
}

2、定义切入点,在AOP类的方法上加入@Pointcut,代码如下:

	/**
	 * 功能说明:切入点定义
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:40:00
	 */
	@Pointcut("execution(* com.wongoing.service.*.*(..))")
	public void executeService() {
		
	}

@Pointcut:声明一个切入点,切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring Bean的方法执行连接点。所以你可以把切入点看作Spring Bean上方法执行的匹配。
一个切入点声明有两个部分:

  • 一个保航名字和任意参数的签名,
  • 还有一个切入点表达式,该表达式决定了我们关注哪个方法的执行。
    切入点表达式的格式:execution([可见性] 返回类型 [声明类型].方法名(参数)[异常])
    []中的位可选,其他的还支持通配符的试用。
  • *:匹配所有字符;
  • …:一般用于匹配多个包、多个参数;
  • +:表示类及其子类;
  • 运算符有&&、||、!。
    在上面的示例中匹配com.wongoing.service包下的所有类的所有方法。
    3、在AOP类中添加前置通知要执行的内容,如下:
	/**
	 * 功能说明:前置通知,通过方法签名的方式引用上面的切入点定义
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:40:21
	 * @param joinPoint
	 */
	@Before("executeService()")
	public void doBeforeAdvice(JoinPoint joinPoint) {
		logger.info("第一个前置通知执行了...");
		logger.info("-------------------------------");
	}

@Before:前置通知——在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

后置通知

后置返回通知一般是在业务处理完成,需要给用户返回统一的结果时触发,触发的方式和前置通知基本相同。代码如下:

	/**
	 * 功能说明:后置通知
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:39:48
	 * @param joinPoint
	 * @param keys
	 */
	@AfterReturning(value = "execution(* com.wongoing.service.*.*(..))", returning = "keys")
	public void doAfterReturningAdvice(JoinPoint joinPoint, Object keys) {
		logger.info("--------------------------------");
		logger.info("第一个后置返回通知的返回值:" + keys);
	}

@AfterReturning:后置通知——在某连接点正常完成后执行的通知,通常在一个匹配的方法返回时执行。

后置异常通知

后置异常通知一般在统一业务异常处理时使用,当业务收到某种异常时进行相应的处理。后置异常通知的配置方式如下:

	/**
	 * 功能说明:后置异常通知
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:44:15
	 * @param joinPoint
	 * @param exception
	 */
	@AfterThrowing(value = "execution(* com.wongoing.service.*.doAfterThrowing*(..))", throwing = "exception")
	public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
		//输出目标方法名
		logger.info(joinPoint.getSignature().getName());
		if (exception instanceof NullPointerException) {
			logger.info("发生了空指针异常!!!");
		}
	}

@AfterThrowing:异常通知——在方法抛出异常退出时执行的通知。

后置最终通知

后置最终通知的配置方式如下:

	/**
	 * 功能说明:后置最终通知
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 15:00:58
	 * @param joinPoint
	 */
	@After(value = "execution(* com.wongoing.service.*.*(..))")
	public void doAfterAdvice(JoinPoint joinPoint) {
		logger.info("后置最终通知执行了!!!");
	}

@After:最终通知——当某连接点退出时执行的通知(不论是正常返回还是异常退出)。

环绕通知

环绕通知的配置方式如下:

	/**
	 * 功能说明:环绕通知
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 15:01:12
	 * @param proceedingJoinPoint
	 * @return
	 */
	@Around(value = "execution(* com.wongoing.service.*.doAround*(..))")
	public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
		logger.info("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
		try {
			
		} catch(Throwable throwable) {
			throwable.printStackTrace();
		}
		return null;
	}

@Around:环绕通知——包围一个连接点的通知,如方法调用,这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点,或直接返回它自己的返回值,或抛出异常来结束执行。

测试

1、完整通知类的代码如下:

package com.wongoing.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAopAdvice {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	/**
	 * 功能说明:切入点定义
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:40:00
	 */
	@Pointcut("execution(* com.wongoing.service.*.*(..))")
	public void executeService() {
		
	}
	
	/**
	 * 功能说明:前置通知,通过方法签名的方式引用上面的切入点定义
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:40:21
	 * @param joinPoint
	 */
	@Before("executeService()")
	public void doBeforeAdvice(JoinPoint joinPoint) {
		logger.info("第一个前置通知执行了...");
		logger.info("-------------------------------");
	}
	
	/**
	 * 功能说明:后置通知
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:39:48
	 * @param joinPoint
	 * @param keys
	 */
	@AfterReturning(value = "execution(* com.wongoing.service.*.*(..))", returning = "keys")
	public void doAfterReturningAdvice(JoinPoint joinPoint, Object keys) {
		logger.info("--------------------------------");
		logger.info("第一个后置返回通知的返回值:" + keys);
	}
	
	/**
	 * 功能说明:后置异常通知
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:44:15
	 * @param joinPoint
	 * @param exception
	 */
	@AfterThrowing(value = "execution(* com.wongoing.service.*.doAfterThrowing*(..))", throwing = "exception")
	public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
		//输出目标方法名
		logger.info(joinPoint.getSignature().getName());
		if (exception instanceof NullPointerException) {
			logger.info("发生了空指针异常!!!");
		}
	}
	
	/**
	 * 功能说明:后置最终通知
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 15:00:58
	 * @param joinPoint
	 */
	@After(value = "execution(* com.wongoing.service.*.*(..))")
	public void doAfterAdvice(JoinPoint joinPoint) {
		logger.info("后置最终通知执行了!!!");
	}
	
	/**
	 * 功能说明:环绕通知
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 15:01:12
	 * @param proceedingJoinPoint
	 * @return
	 */
	@Around(value = "execution(* com.wongoing.service.*.doAround*(..))")
	public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
		logger.info("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
		try {
			
		} catch(Throwable throwable) {
			throwable.printStackTrace();
		}
		return null;
	}
}

2、在com.wongoing.service包下创建一个业务类UserService.java,代码如下:

package com.wongoing.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * 功能说明:用户业务逻辑处理类
 * 修改说明:
 * @author zheng
 * @date 2020-7-15 8:58:09
 * @version 0.1
 */
@Service
public class UserService {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	/**
	 * 功能说明:用户登录
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 8:57:40
	 * @param userName 用户名
	 * @param userPass 用户密码
	 * @return 成功返回true,失败返回false
	 */
	public boolean login(String userName, String userPass) {
		this.logger.info("正在执行用户登录方法...");
		if (userName.equals("admin") && userPass.equals("123456")) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * 功能说明:测试后置异常通知的业务方法
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:51:31
	 */
	public void doAfterThrowingMethod() {
		this.logger.info("正在执行一个业务方法....1");
		throw new NullPointerException();
	}
	
	/**
	 * 功能说明:测试环绕通知的方法
	 * 修改说明:
	 * @author zheng
	 * @date 2020-7-15 9:53:32
	 */
	public void doAroundMethod() {
		this.logger.info("正在执行业务方法...2");
	}
}

3、在com.wongoing.controller包下创建一个UserController.java控制器类,代码如下:

package com.wongoing.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.wongoing.service.UserService;

@RestController
public class UserController {
	@Autowired
	private UserService userService;
	
	@RequestMapping("/")
	public String home() {
		if (this.userService.login("admin", "123456")) {
			return "欢迎使用Spring Boot!";	
		} else {
			return "登录失败,请重试!";
		}
	}
}

4、修改主类,增加@ComponentScan,代码如下:

package com.wongoing;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = "com.wongoing.aop,com.wongoing.service,com.wongoing.controller")
public class DemoApplication {
	
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

5、在项目的src\test\java目录下新建包

  • com.wongoing.service.test
  • com.wongoing.controller.test
    6、在com.wongoing.service.test包下创建单元测试类UserServiceTests.java,代码如下:
package com.wongoing.service.test;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.wongoing.DemoApplication;
import com.wongoing.service.UserService;

@SpringBootTest(classes = DemoApplication.class)
public class UserServiceTests {

	@Autowired
	private UserService service;
	
	@Test
	public void testLogin() {
		boolean result = this.service.login("admin", "123456");
		Assertions.assertEquals(result, true);
	}
	
	@Test
	public void testDoAfterThrowingMethod() {
		this.service.doAfterThrowingMethod();
	}
	
	@Test
	public void testDoAroundMethod() {
		this.service.doAroundMethod();
	}
}

7、在com.wongoing.controller.test包下创建单元测试类UserControllerTests.java,代码如下:

package com.wongoing.controller.test;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import com.wongoing.DemoApplication;

@SpringBootTest(classes = DemoApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerTests {
	
	@Autowired
	private TestRestTemplate restTemplate;
	
	@Test
	public void testHome() {
		String testUrl = "/";
		ResponseEntity<String> entity = this.restTemplate.getForEntity(testUrl, String.class);
		Assertions.assertEquals(entity.getStatusCode(), HttpStatus.OK);
		Assertions.assertEquals(entity.getBody(), "欢迎使用Spring Boot!");
	}
}

8、以JUnit方式运行UserServiceTests.testLogin()方法,输出日志如下:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

2020-07-15 15:44:27.726  INFO 20672 --- [           main] c.w.s.t.UserServiceTests                 : Starting UserServiceTests on zhenglibing-pc with PID 20672 (started by zheng in E:\sts-workspace\demo)
2020-07-15 15:44:27.739  INFO 20672 --- [           main] c.w.s.t.UserServiceTests                 : No active profile set, falling back to default profiles: default
2020-07-15 15:44:29.800  INFO 20672 --- [           main] o.s.s.c.ThreadPoolTaskExecutor           : Initializing ExecutorService 'applicationTaskExecutor'
2020-07-15 15:44:30.310  INFO 20672 --- [           main] c.w.s.t.UserServiceTests                 : Started UserServiceTests in 2.92 seconds (JVM running for 4.094)
2020-07-15 15:44:30.613  INFO 20672 --- [           main] c.w.a.MyAopAdvice                        : 第一个前置通知执行了...
2020-07-15 15:44:30.613  INFO 20672 --- [           main] c.w.a.MyAopAdvice                        : -------------------------------
2020-07-15 15:44:30.674  INFO 20672 --- [           main] c.w.s.UserService                        : 正在执行用户登录方法...
2020-07-15 15:44:30.674  INFO 20672 --- [           main] c.w.a.MyAopAdvice                        : --------------------------------
2020-07-15 15:44:30.674  INFO 20672 --- [           main] c.w.a.MyAopAdvice                        : 第一个后置返回通知的返回值:true
2020-07-15 15:44:30.675  INFO 20672 --- [           main] c.w.a.MyAopAdvice                        : 后置最终通知执行了!!!
2020-07-15 15:44:30.722  INFO 20672 --- [extShutdownHook] o.s.s.c.ThreadPoolTaskExecutor           : Shutting down ExecutorService 'applicationTaskExecutor'

9、以JUnit方式运行UserControllerTests.testDoAfterThrowingMethod()方法,输出日志如下:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

2020-07-15 15:46:09.442  INFO 22348 --- [           main] c.w.s.t.UserServiceTests                 : Starting UserServiceTests on zhenglibing-pc with PID 22348 (started by zheng in E:\sts-workspace\demo)
2020-07-15 15:46:09.451  INFO 22348 --- [           main] c.w.s.t.UserServiceTests                 : No active profile set, falling back to default profiles: default
2020-07-15 15:46:11.035  INFO 22348 --- [           main] o.s.s.c.ThreadPoolTaskExecutor           : Initializing ExecutorService 'applicationTaskExecutor'
2020-07-15 15:46:11.396  INFO 22348 --- [           main] c.w.s.t.UserServiceTests                 : Started UserServiceTests in 2.235 seconds (JVM running for 3.376)
2020-07-15 15:46:11.620  INFO 22348 --- [           main] c.w.a.MyAopAdvice                        : 第一个前置通知执行了...
2020-07-15 15:46:11.620  INFO 22348 --- [           main] c.w.a.MyAopAdvice                        : -------------------------------
2020-07-15 15:46:11.635  INFO 22348 --- [           main] c.w.s.UserService                        : 正在执行一个业务方法....1
2020-07-15 15:46:11.637  INFO 22348 --- [           main] c.w.a.MyAopAdvice                        : doAfterThrowingMethod
2020-07-15 15:46:11.637  INFO 22348 --- [           main] c.w.a.MyAopAdvice                        : 发生了空指针异常!!!
2020-07-15 15:46:11.637  INFO 22348 --- [           main] c.w.a.MyAopAdvice                        : 后置最终通知执行了!!!
2020-07-15 15:46:11.660  INFO 22348 --- [extShutdownHook] o.s.s.c.ThreadPoolTaskExecutor           : Shutting down ExecutorService 'applicationTaskExecutor'

10、以JUnit方式运行UserServiceTests.testDoAroundMethod()方法,日志输出如下:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

2020-07-15 15:47:35.045  INFO 19712 --- [           main] c.w.s.t.UserServiceTests                 : Starting UserServiceTests on zhenglibing-pc with PID 19712 (started by zheng in E:\sts-workspace\demo)
2020-07-15 15:47:35.054  INFO 19712 --- [           main] c.w.s.t.UserServiceTests                 : No active profile set, falling back to default profiles: default
2020-07-15 15:47:36.716  INFO 19712 --- [           main] o.s.s.c.ThreadPoolTaskExecutor           : Initializing ExecutorService 'applicationTaskExecutor'
2020-07-15 15:47:37.103  INFO 19712 --- [           main] c.w.s.t.UserServiceTests                 : Started UserServiceTests in 2.342 seconds (JVM running for 3.479)
2020-07-15 15:47:37.310  INFO 19712 --- [           main] c.w.a.MyAopAdvice                        : 环绕通知的目标方法名:doAroundMethod
2020-07-15 15:47:37.329  INFO 19712 --- [extShutdownHook] o.s.s.c.ThreadPoolTaskExecutor           : Shutting down ExecutorService 'applicationTaskExecutor'

11、以JUnit方式运行UserControllerTests.testHome()方法,日志输出如下:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

2020-07-15 15:48:23.121  INFO 6900 --- [           main] c.w.c.t.UserControllerTests              : Starting UserControllerTests on zhenglibing-pc with PID 6900 (started by zheng in E:\sts-workspace\demo)
2020-07-15 15:48:23.129  INFO 6900 --- [           main] c.w.c.t.UserControllerTests              : No active profile set, falling back to default profiles: default
2020-07-15 15:48:25.024  INFO 6900 --- [           main] o.s.b.w.e.t.TomcatWebServer              : Tomcat initialized with port(s): 0 (http)
2020-07-15 15:48:25.043  INFO 6900 --- [           main] o.a.c.c.StandardService                  : Starting service [Tomcat]
2020-07-15 15:48:25.043  INFO 6900 --- [           main] o.a.c.c.StandardEngine                   : Starting Servlet engine: [Apache Tomcat/9.0.36]
2020-07-15 15:48:25.141  INFO 6900 --- [           main] o.a.c.c.C.[.[.[/]                        : Initializing Spring embedded WebApplicationContext
2020-07-15 15:48:25.141  INFO 6900 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1992 ms
2020-07-15 15:48:25.676  INFO 6900 --- [           main] o.s.s.c.ThreadPoolTaskExecutor           : Initializing ExecutorService 'applicationTaskExecutor'
2020-07-15 15:48:26.044  INFO 6900 --- [           main] o.s.b.w.e.t.TomcatWebServer              : Tomcat started on port(s): 26652 (http) with context path ''
2020-07-15 15:48:26.052  INFO 6900 --- [           main] c.w.c.t.UserControllerTests              : Started UserControllerTests in 3.252 seconds (JVM running for 4.383)
2020-07-15 15:48:26.357  INFO 6900 --- [o-auto-1-exec-1] o.a.c.c.C.[.[.[/]                        : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-07-15 15:48:26.357  INFO 6900 --- [o-auto-1-exec-1] o.s.w.s.DispatcherServlet                : Initializing Servlet 'dispatcherServlet'
2020-07-15 15:48:26.386  INFO 6900 --- [o-auto-1-exec-1] o.s.w.s.DispatcherServlet                : Completed initialization in 29 ms
2020-07-15 15:48:26.412  INFO 6900 --- [o-auto-1-exec-1] c.w.a.MyAopAdvice                        : 第一个前置通知执行了...
2020-07-15 15:48:26.412  INFO 6900 --- [o-auto-1-exec-1] c.w.a.MyAopAdvice                        : -------------------------------
2020-07-15 15:48:26.427  INFO 6900 --- [o-auto-1-exec-1] c.w.s.UserService                        : 正在执行用户登录方法...
2020-07-15 15:48:26.428  INFO 6900 --- [o-auto-1-exec-1] c.w.a.MyAopAdvice                        : --------------------------------
2020-07-15 15:48:26.428  INFO 6900 --- [o-auto-1-exec-1] c.w.a.MyAopAdvice                        : 第一个后置返回通知的返回值:true
2020-07-15 15:48:26.428  INFO 6900 --- [o-auto-1-exec-1] c.w.a.MyAopAdvice                        : 后置最终通知执行了!!!
2020-07-15 15:48:27.029  INFO 6900 --- [extShutdownHook] o.s.s.c.ThreadPoolTaskExecutor           : Shutting down ExecutorService 'applicationTaskExecutor'

通过对各测试运行日志的观察,可以发现返回结果与我们期望的结果相同。

AOP总结

AOP为我们提供了一种以切面方式编程的方法,在业务处理过程中,日志的记录、数据源的切换、事务的处理都可以用AOP的方式解决,而跟Spring Boot的整合,让我们开发AOP又进一步的简化,使面向对象能够更加方便地发挥所长。

案例下载

案例下载

本文地址:https://blog.csdn.net/zlbdmm/article/details/107356307