第三阶段实战(七)——AOP切面编程

1 Spring AOP简介

1.1 AOP 概述

1.1.1 AOP 是什么?

AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切 面编程,它是面向对象编程(OOP)的一种补充和完善。它以通过预编译方式和运行期动态 代理方式,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。如图 :
在这里插入图片描述
AOP与OOP字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目 中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些 对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象 运行时动态织入一些扩展功能或控制对象执行。

1.1.2 AOP 应用场景分析?

实际项目中通常会将系统分为两大部分,一部分是核心业务,一部分是非核业务。
在 编程实现时我们首先要完成的是核心业务的实现,非核心业务一般是通过特定方式切入到 系统中,这种特定方式一般就是借助AOP进行实现。
AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些 扩展功能并可以"控制"对象的执行。例如AOP应用于项目中的日志处理,事务处理,权 限处理,缓存处理等等。如图:
在这里插入图片描述
思考:现有一业务,在没有AOP编程时,如何基于OCP原则实现功能扩展?

1.1.3 Spring AOP 应用原理分析(先了解)?

Spring AOP底层基于代理机制(动态方式)实现功能扩展:

  1. 假如目标对象(被代理对象)实现接口,则底层可以采用JDK动态代理机制为目标 对象创建代理对象(目标类和代理类会实现共同接口)。
  2. 假如目标对象(被代理对象)没有实现接口,则底层可以采用CGLIB代理机制为目 标对象创建代理对象(默认创建的代理类会继承目标对象类型)。
    Spring AOP 原理分析,如图:
    在这里插入图片描述
    说明:Spring boot2.x 中AOP现在默认使用的CGLIB代理,假如需要使用JDK动态代理 可以在配置文件(applicatiion.properties)中进行如下配置:
    spring.aop.proxy-target-class=false

1.2 Spring 中AOP 相关术语分析

切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect声明)。
通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),例如 around,before,after等。
连接点(joinpoint):程序执行过程中某个特定的点,一般指向被拦截到的目标方法。
切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集 合。
连接点与切入点定义如图:
在这里插入图片描述
说明:我们可以简单的将机场的一个安检口理解为连接点,多个安检口为切入点,安 全检查过程看成是通知。总之,概念很晦涩难懂,多做例子,做完就会清晰。先可以按白 话去理解。

2 Spring AOP快速实践

2.1 业务描述

基于项目中的核心业务,添加简单的日志操作,借助SLF4J日志API输出目标方法的执 行时长。(前提,不能修改目标方法代码-遵循OCP原则)

2.2 项目创建及配置

创建maven项目或在已有项目基础上添加AOP启动依赖:

<dependency>        
	<groupId>org.springframework.boot</groupId>        
	<artifactId>spring-boot-starter-aop</artifactId> 
</dependency>

说明:基于此依赖spring可以整合AspectJ框架快速完成AOP的基本实现。AspectJ 是一个面向切面的框架,他定义了AOP的一些语法,有一个专门的字节码生成器来生成遵 守java规范的class文件。

2.3 扩展业务分析及实现

2.3.1 创建日志切面类对象

将此日志切面类作为核心业务增强(一个横切面对象)类,用于输出业务执行时长, 其关键代码如下:

package​ com.cy.pj.common.aspect; 
@Aspect 
@Slf4j 
@Component 
public​ ​class​ SysLogAspect {  
	@Pointcut("bean(sysUserServiceImpl)")public​ ​voiddoLogPointCut() {} 
	@Around("doLogPointCut()")public​ Object around(ProceedingJoinPoint ​jp​) throws​ Throwable{try{    ​
			log.info("start:{}"+System.currentTimeMillis());    
			Object ​result​=​jp​.proceed();//最终会调用目标方法    
			​log ​ .info("after:{}"+System.currentTimeMillis());return​ ​result​;  
		}catch(Throwable ​e​) {    
			​log ​ .error("after:{}",​e​.getMessage());throw​ ​e​;  
		}  
	} 
} 

说明:
@Aspect 注解用于标识或者描述AOP中的切面类型,基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行。
@Pointcut注解用于描述切面中的方法,并定义切面中的切入点(基于特定表达式的 方式进行描述),在本案例中切入点表达式用的是bean表达式,这个表达式以bean开 头,bean括号中的内容为一个spring管理的某个bean对象的名字。
@Around注解用于描述切面中方法,这样的方法会被认为是一个环绕通知(核心业务 方法执行之前和之后要执行的一个动作),@Aournd注解内部value属性的值为一个 切入点表达式或者是切入点表达式的一个引用(这个引用为一个@PointCut注解描述的 方法的方法名)。
ProceedingJoinPoint类为一个连接点类型,此类型的对象用于封装要执行的目标方法相关的一些信息。只能用于 @Around注解描述的方法参数。
当我们切入点引入不正确时,会出现如图所示错误:
在这里插入图片描述

2.3.2 业务切面测试实现

启动项目测试或者进行单元测试,其中Spring Boot项目中的单元测试代码如下:

@SpringBootTest 
public​ ​class​ AopTests {  
​@​Autowired  ​
private​ SysUserService ​userService​;@Testpublic​ ​voidtestSysUserService() {  
		PageObject<SysUserDept> ​po​=  ​userService​.findPageObjects("admin",1);  
		System.​out.println("rowCount:"+​po​.getRowCount());  
	} 
} 

对于测试类中的userService对象而言,它有可能指向JDK代理,也有可能指向CGLIB代 理,具体是什么类型的代理对象,要看application.yml配置文件中的配置.

2.3.3 应用总结分析

在业务应用,AOP相关对象分析,如图:
在这里插入图片描述

2.4 扩展业务织入增强分析

2.4.1 基于JDK代理方式实现

假如目标对象有实现接口,则可以基于JDK为目标对象创建代理对象,然后为目标对象 进行功能扩展,如图:
在这里插入图片描述
说明:假如目标对象类型没有实现接口,则不允许使用JDK代理。

2.4.2 基于CGLIB代理方式实现

假如目标对象没有实现接口(当然实现了接口也是可以的),可以基于CGLIB代理方式 为目标对象织入功能扩展,如图:
在这里插入图片描述
说明:目标对象实现了接口也可以基于CGLIB为目标对象创建代理对象。但是目标对 象类型假如使用了final修饰,则不可以使用CGBLIB。

3 Spring AOP编程增强

3.1 切面通知应用增强

3.1.1 通知类型

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类
型的通知(通知-Advice描述的是一种扩展业务),它们分别是:
▪ @Before
▪ @AfterReturning
▪ @AfterThrowing
▪ @After
▪ @Around.重点掌握(优先级最高)
说明:在切面类中使用什么通知,由业务决定,并不是说,在切面中要把所有通知都写 上。

3.1.2 通知执行顺序

假如这些通知全部写到一个切面对象中,其执行顺序及过程,如图所示(但是这个 顺序现在在springboot工程的不同版本中可能会有一些不同):
在这里插入图片描述
说明:实际项目中可能不会在切面中定义所有的通知,具体定义哪些通知要结合业务 进行实现。

3.1.3 通知实践过程分析

代码实践分析如下:

package com.cy.pj.common.aspect; 
@Component 
@Aspect 
public​ ​class​ SysTimeAspect { 
	@Pointcut("bean(sysUserServiceImpl)") 
	public​ ​voiddoTime(){}
	@Before("doTime()") 
	public​ ​voiddoBefore(){ 
		System.​out.println("time doBefore()"); 
	} 
	@After("doTime()") 
	public​ ​voiddoAfter(){ 
		System.​out.println("time doAfter()"); 
	} 
	/**核心业务正常结束时执行* 说明:假如有after,先执行after,再执行 returning*/ 
	@AfterReturning("doTime()") 
	public​ ​voiddoAfterReturning(){ 
		System.​out.println("time doAfterReturning"); 
	} 
	/**核心业务出现异常时执行说明:假如有after,先执行after,再执行 Throwing*/ 
	@AfterThrowing("doTime()") 
	public​ ​voiddoAfterThrowing(){ 
		System.​out.println("time doAfterThrowing"); 
	} 
	@Around("doTime()") 
	public​ Object doAround(ProceedingJoinPoint ​jp​) throws​ Throwable{ 
		System.​out.println("doAround.before");          
		try{  
			Object ​obj​=​jp​.proceed();            
			System.​out.println("doAround.after");           
			return​ ​obj​;  
		}catch(Throwable e){           
			System.​out.println(​e.getMessage());           
			throw e;          
		} 
	} 
} 

说明:
对于@AfterThrowing通知只有在出现异常时才会执行,所以当做一些异常监控时 可在此方法中进行代码实现。
课堂练习:定义一个异常监控切面,对目标页面方法进行异常监控,并以日志信息的 形式输出异常

package​ com.cy.pj.common.aspect; 
import​ lombok.extern.slf4j.Slf4j; 
@Slf4j 
@Aspect 
@Component 
public​ ​class​ SysExceptionAspect { 
	@AfterThrowing(pointcut="bean(*ServiceImpl)",throwing ="e") 
	public​ ​voiddoHandleException(JoinPoint ​jp​,Throwable ​e​) { 
		MethodSignature ​ms​=(MethodSignature)​jp​.getSignature(); 
		log.error("{}'exception msg is  {}",​ms​.getName(),​e​.getMessage()); 
	}
}

说明:AfterThrowing中throwing属性的值,需要与它描述的方法的异常参数名相同。假 如不同会有如下异常,例如:
在这里插入图片描述

3.2 切入点表达式增强

Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明:
在这里插入图片描述

3.2.1 bean表达式(重点)

bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
▪ bean(“userServiceImpl”)指定一个userServiceImpl类中所有方法。
▪ bean("*ServiceImpl")指定所有后缀为ServiceImpl的类中所有方法。
说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中某个bean的name。

3.2.2 within表达式(了解)

within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
▪ within(“aop.service.UserServiceImpl”)指定当前包中这个类内部的所有方法。
▪ within(“aop.service.") 指定当前目录下的所有类的所有方法。
▪ within("aop.service…
”) 指定当前目录以及子目录中类的所有方法。
within表达式应用场景分析:
1)对所有业务bean都要进行功能增强,但是bean名字又没有规则。
2)按业务模块(不同包下的业务)对bean对象进行业务功能增强。

3.2.3 execution表达式(了解)

execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:
▪ execution(返回值类型 包名.类名.方法名(参数列表))。
▪ execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
▪ execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为 String的addUser方法。
▪ execution(* aop.service….(…)) 万能配置。

3.2.4 @annotation表达式(重点)

@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
▪ @annotation(anno.RequiredLog) 匹配有此注解描述的方法。
▪ @annotation(anno.RequiredCache) 匹配有此注解描述的方法。 其中:​RequiredLog​为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层 方法时,系统底层会在执行此方法时进行日扩展操作。
课堂练习:定义一Cache相关切面,使用注解表达式定义切入点,并使用此注解对需要使 用cache的业务方法进行描述,代码分析如下: 第一步:定义注解RequiredCache

package​ com.cy.pj.common.annotation; 
/**  * 自定义注解,一个特殊的类,所有注解都默认继承Annotation接口  */ 
@Retention(RetentionPolicy.​RUNTIME ​ ) 
@Target(ElementType.​METHOD ​ ) 
public​ ​@interface​ ​RequiredCache​ {//... 
}

第二步:定义SysCacheAspect切面对象。

package​ com.cy.pj.common.aspect; 
@Aspect 
@Component 
public​ ​class​ SysCacheAspect { 
	@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)")public​ ​voiddoCache() {}@Around("doCache()")public​ Object around(ProceedingJoinPoint ​jp​) throws​ Throwable{   
		System.​out.println("Get data from cache");   
		Object ​obj​=​jp​.proceed();   
		System.​out.println("Put data to cache");return​ ​obj​;   
	}   
} 

第三步:使用@RequiredCache注解对特定业务目标对象中的查询方法进行描述(这里 以部门模块的查询方法为例)。

 @RequiredCache 
 @Override 
 public​ List<Map<String, ​Object​>> findObjects() { 
	return​ ​list​; 
}

第四步:进行部门模块的访问测试,分析其结果.

3.3 切面优先级设置实现

切面的优先级需要借助@Order注解进行描述,数字越小优先级越高,默认优先级比较 低。
例如: 定义日志切面并指定优先级。

@Order(1) 
@Aspect 
@Component 
public class SysLogAspect {} 

定义缓存切面并指定优先级:

@Order(2)
@Aspect 
@Component 
public class SysCacheAspect {}

说明:当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过 滤器链、拦截器链,其执行分析如图
在这里插入图片描述

3.4 关键对象与术语总结

Spring 基于AspectJ框架实现AOP设计的关键对象概览,如图
在这里插入图片描述

3.5 用户行为日志记录实现(实践)

本小节作为课后作业,以AOP方式记录项目中的用户行为信息,并将其存储到数据 库。参考日志模块的文档。

4 Spring AOP事务处理

4.1 Spring 中事务简介

4.1.1 事务定义

事务(Transaction)是一个业务,是一个不可分割的逻辑工作单元,基于事务可以更 好的保证业务的正确性。

4.1.2 事务特性

事务具备ACID特性,分别是:
▪ 原子性(Atomicity):一个事务中的多个操作要么都成功要么都失败。
▪ 一致性(Consistency): 例如存钱操作,存之前和存之后的总钱数应该是一致的。
▪ 隔离性(Isolation):事务与事务应该是相互隔离的。
▪ 持久性(Durability):事务一旦提交,数据要持久保存。
说明:目前市场上在事务一致性方面,通常会做一定的优化,比方说只要最终一致就可以了,这 样的事务我们通常会称之为柔性事务(只要最终一致就可以了).

4.2 Spring 中事务管理

4.2.1 Spring 中事务方式概述

Spring框架中提供了一种声明式事务的处理方式,此方式​基于AOP代理,可以将具体业 务逻辑与事务处理进行解耦​。也就是让我们的业务代码逻辑不受污染或少量污染,就可以实 现事务控制。
在SpringBoot项目中,其内部提供了事务的自动配置,当我们在项目中添加了指定依赖​spring-boot-starter-jdbc​时,框架会自动为我们的项目注入事务管理器对象,最常 用的为DataSourceTransactionManager​对象。

4.2.2 Spring 中事务管理实现

本小节重点讲解实际项目中最常用的注解方式的事务管理,以注解@Transactional 配置方式为例,进行实践分析。
基于@Transactional 注解进行声明式事务管理的实现步骤分为两步:

  1. 启用声明式事务管理,在项目启动类上添加@EnableTransactionManagement, 新版本中也可不添加(例如新版Spring Boot项目)。
  2. 将@Transactional注解添加到合适的业务类或方法上,并设置合适的属性信息。

其代码示例如下:

@Transactional(timeout = 30,
				readOnly =false,
				isolation = Isolation.​READ_COMMITTED ,                
				rollbackFor = Throwable.class,                
				propagation = Propagation.​REQUIRED ​ )   
@Service    
public​ ​class​ ​implements​ SysUserService {         
	@Transactional(readOnly =true)     
	@Overridepublic​ PageObject<SysUserDept> findPageObjects( String ​username​, Integer ​pageCurrent​{
	//方法 
	}
}

其中,代码中的@Transactional注解用于描述类或方法,告诉spring框架我们要在此类的方法执行时进行事务控制,其具体说明如下:。
▪ 当@Transactional注解应用在类上时表示类中所有方法启动事务管理,并且一般用于 事务共性的定义。
▪ 当@Transactional描述方法时表示此方法要进行事务管理,假如类和方法上都有 @Transactional注解,则方法上的事务特性优先级比较高。
@Transactional 常用属性应用说明:
▪ timeout:事务的超时时间,默认值为-1,表示没有超时显示。如果配置了具体时间,则 超过该时间限制但事务还没有完成,则自动回滚事务。这个时间的记录方式是在事务 开启以后到sql语句执行之前。
▪ read-only:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为true。对添加,修改,删除业务read-only 的值应该为false。
▪ rollback-for:用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定 ,各类型之间可以通过逗号分隔。
▪ no-rollback- for: 抛出no-rollback-for 指定的异常类型,不回滚事务。
▪ isolation事务的隔离级别,默认值采用 DEFAULT。当多个事务并发执行时,可能会出现脏读,不可重复读,幻读等现象时,但假如不希望出现这些现象可考虑修改事务的 隔离级别(但隔离级别越高并发就会越小,性能就会越差) Spring 中事务控制过程分析,如图
在这里插入图片描述

4.2.3 Spring 中事务传播特性

事务传播(Propagation)特性指"不同业务(service)对象"中的事务方法之间相互调 用时,事务的传播方式,如图:
在这里插入图片描述
其中,常用事务传播方式如下:
▪ @Transactional(propagation=Propagation.REQUIRED) 。
如果没有事务创建新事务, 如果当前有事务参与当前事务, Spring 默认的事务传播 行为是PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链: Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服 务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。如图:
在这里插入图片描述
代码示例如下:

@Transactional(propagation = Propagation.REQUIRED) 
@Override 
public​ List<Node> findZtreeMenuNodes() { 
	return​ ​sysMenuDao​.findZtreeMenuNodes(); 
} 

当有一个业务对象调用如上方法时,此方法始终工作在一个已经存在的事务方法,或 者是由调用者创建的一个事务方法中。
▪ @Transactional(propagation=Propagation.REQUIRES_NEW)。 必须是新事务, 如果有当前事务, 挂起当前事务并且开启新事务,如图:
在这里插入图片描述
代码示例如下:

@Transactional(propagation = Propagation.​REQUIRES_NEW ​ )
@Override 
public​ ​voidsaveObject(SysLog ​entity​) {   ​
	sysLogDao​.insertObject(​entity​); 
}

当有一个 业务对象调用如上业务方法时,此方法会始终运行在一个新的事务中。

4.3 Spring 中事务管理小结

Spring 声明式事务是 Spring 最核心,最常用的功能。
由于 Spring 通过 IOC 和 AOP 的功能非常透明地实现了声明式事务的功能,对于一般的开发者基本上无须了解 Spring声明式事务的内部细节,仅需要懂得如何配置就可以了。但对于中高端开发者还需 要了解其内部机制。

5 Spring AOP 异步操作实现

5.1 异步场景分析

在开发系统的过程中,通常会考虑到系统的性能问题,提升系统性能的一个重要思想 就是“串行”改“并行”。说起“并行”自然离不开“异步”,今天我们就来聊聊如何使用 Spring的@Async的异步注解。

5.2 Spring 业务的异步实现

5.2.1 启动异步配置

在基于注解方式的配置中,借助@EnableAsync注解进行异步启动声明,Spring Boot版的项目中,将@​EnableAsync注解应用到启动类上,​代码示例如下:

@EnableAsync//spring容器启动时会创建线程池  ​  
@SpringBootApplication    
public​ ​class​ Application { 
	public​ ​static​ ​voidmain(String[] ​args​) { 
		SpringApplication.​run ​ (Application.class, ​args​); 
	} 
} 

5.2.2 Spring中@Async注解应用

在需要异步执行的业务方法上,使用@Async方法进行异步声明。

@Async 
@Transactional(propagation = Propagation.​REQUIRES_NEW ​ ) 
@Override
public​ ​voidsaveObject(SysLog ​entity​) {       
	System.​out.println("SysLogServiceImpl.save:"+ Thread.currentThread().getName());   
	sysLogDao​.insertObject(​entity​);   
	//​try​{Thread.​sleep(5000);}​catch​(Exception ​e​) {} 
} 

假如需要获取业务层异步方法的执行结果,可参考如下代码设计进行实现:

@Transactional(propagation = Propagation.​REQUIRES_NEW ​ )    
@Async 
@Override 
public​ Future<Integer> saveObject(SysLog ​entity​) { 
	System.​out.println("SysLogServiceImpl.save:"+ Thread.currentThread().getName()); 
	int​ ​rows​=​sysLogDao​.insertObject(​entity​); 
	//​try​{Thread.​sleep ​ (5000);}​catch​(Exception ​e​) {}     ​
	return​ ​new​ AsyncResult<Integer>(​rows​); 
} 

其中,AsyncResult对象可以对异步方法的执行结果进行封装,假如外界需要异步方法结果时,可以通过Future对象的get方法获取结果。
当我们需要自己对spring框架提供的线程池进行一些简易配置,可以参考如下代码:

spring:   
	task:     
		execution:       
			pool:         
				queue-capacity: 128         
				core-size: 5         
				max-size: 128         
				keep-alive: 60000       
			thread-name-prefix: db-service-task- 

对于spring框架中线程池配置参数的涵义,可以参考ThreadPoolExecutor对象中的 解释。
说明:对于@Async注解默认会基于ThreadPoolTaskExecutor对象获取工作线程,然 后调用由@Async描述的方法,让方法运行于一个工作线程,以实现异步操作。但是假如系 统中的默认拒绝处理策略,任务执行过程的异常处理不能满足我们自身业务需求的话,我可 以对异步线程池进行自定义.(SpringBoot中默认的异步配置可以参考自动配置对象 TaskExecutionAutoConfiguration).

5.2.3 Spring 自定义异步池的实现(拓展)

为了让Spring中的异步池更好的服务于我们的业务,同时也尽量避免OOM,可以自定义 线程池优化设计如下:关键代码如下:

package​ com.cy.pj.common.config
@Slf4j
@Setter 
@Configuration 
@ConfigurationProperties("async-thread-pool") 
public​ ​class​ SpringAsyncConfig ​implements​ AsyncConfigurer{/**核心线程数*/ 
	private​ ​int​ ​corePoolSize​=20; 
	/**最大线程数*/ 
	private​ ​int​ maximumPoolSize=1000; 
	/**线程空闲时间*/ 
	private​ ​int​ ​keepAliveTime​=30; 
	/**阻塞队列容量*/ 
	private​ ​int​ ​queueCapacity​=200; 
	/**构建线程工厂*/ 
	private​ ThreadFactory ​threadFactory​=newThreadFactory() { 
		//CAS算法 
		private​ AtomicInteger ​at​=newAtomicInteger(1000); 
		@Override 
		public​ Thread newThread(Runnable ​r​) { 
			return​ ​newThread(​r​,"db-async-thread-"+​at​.getAndIncrement()); 
		} 
	}; 
	@Overridepublic​ Executor getAsyncExecutor() {         
		ThreadPoolTaskExecutor ​executor​ =new ThreadPoolTaskExecutor();         ​
		executor​.setCorePoolSize(​corePoolSize​);         
		​executor​.setMaxPoolSize(​maximumPoolSize​);         
		​executor​.setKeepAliveSeconds(​keepAliveTime​);         ​
		executor​.setQueueCapacity(​queueCapacity​);         ​
		executor​.setRejectedExecutionHandler((Runnable ​r​,
								ThreadPoolExecutor ​exe​) -> {​log​.warn("当前任务线程池队列已满.");         
		});         ​
		executor​.initialize();return​ ​executor​;     
	} 
 
	@Overridepublic​ AsyncUncaughtExceptionHandler  getAsyncUncaughtExceptionHandler() {return​ ​newAsyncUncaughtExceptionHandler() {             
			@Overridepublic​ ​voidhandleUncaughtException(Throwable ​ex​,Method ​method​,Object... ​params​) {                 
				​log​.error("线程池执行任务发生未知异常.", ​ex​);             
			}         
		};     
	}
}

其中 :@ConfigurationProperties(“async-thread-pool”)的含义是读取 application.yml配置文件中以"async-thread-pool"名为前缀的配置信息,并通过所描 述类的set方法赋值给对应的属性,在application.yml中连接器池的关键配置如下:

async-thread-pool:        
		corePoolSize:20        
		maxPoolSize:1000        
		keepAliveSeconds:30        
		queueCapacity:1000 

后续在业务类中,假如我们使用@Async注解描述业务方法,默认会使用 ThreadPoolTaskExecutor池对象中的线程执行异步任务。

6 Spring AOP中Cache操作实现(拓展)

6.1 缓存场景分析

在业务方法中我们可能调用数据层方法获取数据库中数据,假如访问数据的频率比较 高,为了提高的查询效率,降低数据库的访问压力,可以在业务层对数据进行缓存.

6.2 Spring 中业务缓存应用实现

6.2.1 启动缓存配置

在项目(SpringBoot项目)的启动类上添加@EnableCaching注解,以启动缓存配置。 关键代码如下:

package​ com.cy; 
/** * 异步的自动配置生效).  
* ​@EnableCaching​ 注解表示启动缓存配置  */ 
@EnableCaching 
@SpringBootApplication 
public​ ​class​ Application { 
	public​ ​static​ ​voidmain(String[] ​args​) { 
		SpringApplication.run(Application.class, ​args​); 
	}  
} 

6.2.2 业务方法上应用缓存配置

在需要进行缓存的业务方法上通过@Cacheable注解对方法进行相关描述.表示方法的 返回值要存储到Cache中,假如在更新操作时需要将cache中的数据移除,可以在更新方法 上使用@CacheEvict注解对方法进行描述。
例如: 第一步:在相关模块查询相关业务方法中,使用缓存,关键代码如下:

@Cacheable(value = "menuCache") 
@Transactional(readOnly =true) 
public​ List<Map<String,Object>> findObjects() { .... }

其中,value属性的值表示要使用的缓存对象,名字自己指定,其中底层为一个map对 象,当向cache中添加数据时,key默认为方法实际参数的组合。
第二步:在相关模块更新时,清除指定缓存数据,关键代码如下:

 @CacheEvict(value="menuCache",​allEntries​=true)  
 @Overridepublic​ ​intsaveObject(SysDept entity) {...} 

其中,allEntries表示清除所有。
说明:spring中的缓存应用原理,如图:
在这里插入图片描述

6.2.3 Spring中自定义缓存的实现(拓展)

在Spring中默认cache底层实现是一个Map对象,假如此map对象不能满足我们实际需 要,在实际项目中我们可以将数据存储到第三方缓存系统中.

7 Spring AOP原生方式实现(拓展)

7.1 概述

Spring 整合AspectJ框架实现AOP只是Spring框架中AOP的一种实现方式,此方式相对比较简单,实现方便。但此方式底层还是要转换为Spring原生AOP的实现,Spring AOP原生方式实现的核心有两大部分构成,分别是:
▪ 代理(JDK,CGLIB)。
▪ org.aopalliance包下的拦截体系。

7.2 案例架构分析

本小节以Spring中一种原生AOP架构的基本实现为例进行原理分析和说明,其简易架 构如图
在这里插入图片描述
其中 DefaultAdvisorAutoProxyCreator这个类功能更为强大,这个类的奇妙之处是 他实现BeanPostProcessor接口,当ApplicationContext读取所有的Bean配置信息后, 这个类将扫描上下文,寻找所有的Advisor对象(一个Advisor由切入点和通知组成),将这些Advisor应用到所有符合切入点的Bean中。

7.3 案例业务实现

7.3.1 业务描述

创建SpringBoot项目,并基于Spring原生AOP的实现为特定业务对象添加简易日志实 现。

7.3.2 核心业务接口定义及实现

定义RequiredLog注解,用于描述目标业务对象

package​ com.cy.pj.mail.annotation; 
import​ java.lang.annotation.ElementType; 
import​ java.lang.annotation.Retention; 
import​ java.lang.annotation.RetentionPolicy; 
import​ java.lang.annotation.Target; 
@​Retention(RetentionPolicy.RUNTIME) 
@​Target(ElementType.METHOD) 
public​ ​@interface​ RequiredLog { }

定义邮件业务接口,用于定义搜索业务规范

package​ com.cy.pj.mail.service; 
public​ ​interface​ SearchService {   
	boolean sendMsg(String msg); 
}

定义邮件业务接口实现,并使用requiredLog注解描述

package​ com.cy.pj.mail.aop; 
import​ org.springframework.stereotype.Service; 
import​ com.cy.spring.annotation.RequiredLog; 
@​Service 
public​ ​class​ MailServiceImpl ​implements​ MailService { 
	@​RequiredLog 
	@​Override 
	publicboolean search(String msg) { 
		System.out.println("send "+ key); 
		return​ ​true; 
	} 
}

7.3.3 日志Advice对象定义

定义LogAdvice对象,基于此对象为目标业务对象做日志增强。

package​ com.cy.pj.mail.aop; 
import​ org.aopalliance.intercept.MethodInterceptor; 
import​ org.aopalliance.intercept.MethodInvocation; 
public​ ​class​ LogAdvice ​implements​ MethodInterceptor { 
	@​Override 
	public​ Object invoke(MethodInvocation invocation) throws​ Throwable { 
		System.out.println("start:"+System.currentTimeMillis()); 
		Object result=invocation.proceed(); 
		System.out.println("after:"+System.currentTimeMillis()); 
		return​ result; 
	} 
}

其中,MethodInterceptor对象继承Advice对象,基于此对象方法可以对目标方法 进行拦截。

7.3.4 日志Advisor对象定义及实现

创建日志Advisor对象,在对象内部定义要切入扩展功能的点以及要应用的通知 (Advice)对象。

package​ com.cy.spring.advisor; 
import​ java.lang.reflect.Method; 
import​ org.springframework.stereotype.Component; 
import​ com.cy.spring.annotation.RequiredLog; 
@​Component 
public​ ​class​ LogAdvisor ​extends​ StaticMethodMatcherPointcutAdvisor { 
	private​ ​static​ ​final​ ​long​ serialVersionUID = 7022316764822635205L; 
	publicLogMethodMatcher() { 
		//在特定切入点上要执行的通知 
		setAdvice(newLogAdvice()); 
	} 
	//Pointcut 
	//方法返回值为true时,则可以为目标方法对象创建代理对象 
	@​Override 
	public​ ​booleanmatches(Method method,Class<?> targetClass) { 
		try{ 
			Method targetMethod= targetClass.getMethod(method.getName(),
			method.getParameterTypes()); 
			return​ targetMethod.isAnnotationPresent(RequiredLog.class); 
		}catch(Exception e) { 
			return​ ​false; 
		} 
	}
}

其中,StaticMethodMatcherPointcutAdvisor类为Spring框架中定义的一种 Advisor,我们自己写的Advisor可以直接继承此类进行资源整合。

7.3.5 BeanPostProcessor类型对象初始化

在项目启动类中,添加DefaultAdvisorAutoProxyCreator对象初始化方法,基于 此对象在容器启动时扫描所有Advisor对象,然后基于切入点描述的目标方法为目标对象创 建代理对象,代码如下:

@Bean  
public​ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator​() { 
	return​ ​newDefaultAdvisorAutoProxyCreator(); 
} 

7.3.6 日志业务单元测试实现

基于Spring boot项目进行单元测试:

package​ com.cy; 
import​ org.junit.Test; 
import​ org.junit.runner.RunWith; 
import​ org.springframework.beans.factory.annotation.Autowired; 
import​ org.springframework.boot.test.context.SpringBootTest; 
import​ org.springframework.test.context.junit4.SpringRunner; 
import​ com.cy.spring.aop.SearchService; 
@​SpringBootTest 
public​ ​class​ CgbSbootAop01ApplicationTests { 
	@​Autowired 
	private​ SearchService searchService; 
	@​Test 
	public​ ​voidtestSearch() { 
		//System.out.println(searchService); 
		searchService.search("tedu"); 
	} 
} 

说明:在spring 框架中,很多功能都是原生AOP进行了功能的扩展和实现。

8 总结

8.1 重难点分析

▪ AOP 是什么,解决了什么问题,应用场景?
▪ AOP 编程基本步骤及实现过程(以基于AspectJ框架实现为例)。
▪ AOP 编程中的核心对象及应用关系。(代理对象,切面对象,通知,切入点)
▪ AOP 思想在Spring中的实现原理分析。(基于代理方式进行扩展业务的织入)
▪ AOP 编程中基于注解方式的配置实现。(@Aspect,@PointCut,@Around,…)
▪ AOP 编程中基于注解方式的事务控制。(@Transactional)
▪ AOP 编程中异步操作的实现?(@EnableAsync,@Async,配置)
▪ AOP 编程中的缓存应用?(@EnableCaching,@Cacheable,@CacheEvict)

8.2 FAQ分析

▪ 什么是OCP原则(开闭原则)?
▪ 什么是DIP原则 (依赖倒置)?
▪ 什么是单一职责原则(SRP)?
▪ Spring 中AOP的有哪些配置方式?(XML,注解)
▪ Spring 中AOP 的通知有哪些基本类型?(5种)
▪ Spring 中AOP是如何为Bean对象创建代理对象的?(JDK,CGLIB)
▪ Spring 中AOP切面的执行顺序如何指定?(@Order)
▪ Spring 单体架构项目中事务的控制要通过Connection对象实现,?
▪ Spring 如何保证一个线程一个Connection对象?借助ThreadLocal实现.?
▪ 多个事务并发执行时可能会出现什么问题?(脏读,不可重复读,幻影读)
▪ 你了解事务的隔离级别吗?知道具体的应用场景吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值