文章目录


1 Spring AOP简介

1.1 AOP 概述

1.1.1 AOP 是什么?

AOP( Aspect Orient Programming )是一种设计思想
是软件设计领域中的 面向切面编程 ,它是面向对象编程(OOP)的一种补充和完善。

实际项目中我们通常将面向对象理解为一个静态过程
(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性)
面向切面理解为一个动态过程
(在对象运行时动态织入一些扩展功能或控制对象执行)。

如图-1所示:

图-1
.

  • logging - 日志切面
  • security - 安全切面

1.1.2 AOP 应用场景分析?

实际项目中通常会将系统分为两大部分

  • 一部分是核心业务
  • 一部分是非核业务

在编程实现时我们首先要完成的是核心业务的实现,<mark>非核心业务一般是通过特定方式切入到系统中,这种特定方式一般就是借助AOP进行实现</mark>。

AOP就是要基于OCP(开闭原则)
在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以"控制"对象的执行。

# 常见设计原则

  • 单一职责原则 - ioc
  • 依赖倒置原则 - ioc
  • 开闭原则(ocp) - aop

例如AOP应用于项目中的 日志处理事务处理权限处理缓存处理 等等。

如图-2所示:


图-2

<mark>思考:现有一业务,在没有AOP编程时,如何基于OCP原则实现功能扩展?</mark>

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

Spring AOP底层基于***机制实现功能扩展:

  1. 假如目标对象(被***对象)实现接口,则底层可以采用JDK动态***机制为目标对象创建***对象(目标类和***类会实现共同接口)。
  2. 假如目标对象(被***对象)没有实现接口,则底层可以采用CGLIB***机制为目标对象创建***对象(默认创建的***类会继承目标对象类型)。
    Spring AOP 原理分析,如图-3所示:


图-3

说明:Spring boot2.x 中AOP现在默认使用的CGLIB***,假如需要使用JDK动态***可以在配置文件(applicatiion.properties)中进行如下配置:

spring.aop.proxy-target-class=false

1.2 AOP 相关术语分析

切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect声明)。
通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等。
切入点(pointcut):对连接点拦截内容的一种定义,一般可以理解为多个连接点的结合。
连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的的方法。
连接点与切入点定义如图-4所示:


图-4

说明:概念很晦涩难懂,多做例子,做完就会清晰。先可以按白话去理解。


文章目录


2 Spring AOP 快速实践

2.1 业务描述

基于项目中的核心业务,添加简单的日志操作,借助SLF4J日志API输出目标方法的执行时长。

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 注解描述的类型为一个 AOP 应用中的切面类型,此类的构成: * 1)切入点 (在哪些方法执行时,我们要植入扩展功能):例如所有地铁站的入口 * 2)通知 (要植入的扩展功能):例如安检 * * @author Administrator * */
@Aspect
@Component
@Slf4j
public class SysLogAspect {
		
		@Pointcut 注解用于描述方法,描述的方法不需要写具体内容 , 
		底层运行时,会获取方法上的注解,并拿到注解中定义的表达式,
		如:bean() 为一种切入点表达式的定义方式 。 
		()中的内容为一个bean对象或者一堆bean对象
		(例如*ServiceImpl)
		系统底层会认为bean表达式描述的对象中所有方法的集合为切入点,
		---------------------------------------------------
		当 spring管理的 bean -  sysUserServiceImpl  所有方法
		被执行,
		调用目标方法
			↓
	 @Pointcut("bean(sysUserServiceImpl)")
	 public void logPointCut() {}



	@Around 环绕方法 - 切入切点前后
	在切面类型中要使用特殊标记进行描述,这个标记为注解
		↓
		↓		jp 连接点(JoinPoint):对应切入点描述的方法中的一个目标方法
		↓		调用的方法 (ProceedingJoinPoint - 执行中插入的切点)
		↓			↓					|
	 @Around("logPointCut()")public Object around(ProceedingJoinPoint jp) throws Throwable{
					↑
				扩展功能的实现
					↓
		 try {
		   log.info("start:"+System.currentTimeMillis());
		   
		   Object result=jp.proceed();--- 调用下一个切面方法或目标方法
		   
		   log.info("after:"+System.currentTimeMillis());
		   
		   return result;- - - - 目标方法的执行结果 返回 给 controller
		   
		 }catch(Throwable e) {
		   log.error(e.getMessage());--- 日志打印异常
		   throw e;
		 }
	 }
}

说明:

  • @Aspect 注解用于标识或者描述AOP中的切面类型
    基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行。
  • @Pointcut 注解用于描述切面中的方法
    并定义切面中的切入点(基于特定表达式的方式进行描述)
    在本案例中切入点表达式用的是bean表达式
    这个表达式以bean开头,bean括号中的内容为一个spring管理的某个bean对象的名字。
  • @Around 注解用于描述切面中方法
    这样的方***被认为是一个环绕通知(核心业务方法执行之前和之后要执行的一个动作)
    @Aournd注解内部value属性的值为一个切入点表达式或者是切入点表达式的一个引用
    (这个引用为一个@PointCut注解描述的方法的方法名)。
  • ProceedingJoinPoint类 为一个连接点类型
    此类型的对象用于封装要执行的目标方法相关的一些信息。
    <mark>一般用于@Around注解描述的方法参数</mark>。

2.3.2 业务切面测试实现

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

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

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

2.3.3 应用总结分析

在业务应用,AOP相关对象分析,如图-5所示:


图-5

2.4 扩展业务织入增强分析

2.4.1 基于JDK***方式实现

假如目标对象有实现接口,则可以基于JDK为目标对象创建******对象,然后为目标对象进行功能扩展,如图-6所示:


图-6

#spring
spring:
  aop:
    proxy-target-class: false 
  # true 默认 使用继承***
  # false 使用 组合***(jdk***) 

2.4.2 基于CGLIB***方式实现 ( Code Generator Library )

假如目标对象没有实现接口,可以基于CGLIB***方式为目标织入功能扩展,如图-7所示:


图-7

说明:目标对象实现了接口也可以基于CGLIB为目标对象创建***对象。


文章目录


3 Spring AOP编程增强

3.1 切面通知应用增强

3.1.1 通知类型

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知,它们分别是:

  • 前置通知 (@Before) 。
  • 返回通知 (@AfterReturning) 。
  • 异常通知 (@AfterThrowing) 。
  • 后置通知 (@After)。
  • 环绕通知 (@Around) : <mstyle mathcolor="&#35;f01"> </mstyle> \color{#f01}{重点掌握} (<mark>优先级最高</mark>)

说明:所有的通知,在切面对象中都会有对应的方法,这些方***使用特定注解进行描述,不同注解描述的方法,它的执行时间点是不一样的

3.1.2 通知执行顺序

假如这些通知全部写到一个切面对象中,其执行顺序及过程,如图-8所示:




图-8

<mark>另外</mark>
如果 @Round 中如果没有 try catch ,在出现异常后,会不会回到 Round 切面

3.1.3 通知实践过程分析

代码实践分析如下:

@Service
@Aspect
public class SysTimeAspect {
	@Pointcut("bean(sysUserServiceImpl)")
	public void doTime(){}


	@Before("doTime()")
	public void doBefore(JoinPoint jp){
		System.out.println("time doBefore()");  → 	可以做:参数校验
	}

			核心业务出现异常时执行
                 说明:假如有after,先执行after,再执行Throwing (类似 finally )@After("doTime()")
	public void doAfter(){
		System.out.println("time doAfter()");
	}

	核心业务正常结束时执行
	说明:假如有after,先执行after,再执行returning
		|@AfterReturning("doTime()")
	public void doAfterReturning(){
		System.out.println("time doAfterReturning");
	}

				核心业务出现异常时执行
                 说明:假如有after,先执行after,再执行Throwing (类似 finally )@AfterThrowing("doTime()")
	public void doAfterThrowing(){
		System.out.println("time doAfterThrowing");
	}
	@Around("doTime()")
	public Object doAround(ProceedingJoinPoint jp)
			throws Throwable{
		System.out.println("doAround.before");
		Object obj=jp.proceed();
		System.out.println("doAround.after");
		return obj;
	}
}

说明:对于 @AfterThrowing 通知只有在出现异常时才会执行,所以当做一些异常监控时可在此方法中进行代码实现。


课堂练习:定义一个异常监控切面,对目标页面方法进行异常监控,并以日志形式输出异常信息?

package com.cy.pj.common.aspect;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Aspect
@Component
public class SysExceptionAspect {

	异常通知:
	jp 连接点对象(指向了正在执行的 目标方法)
	e 此变量 用于接收执行目标方法出现的异常
				↓
	@AfterThrowing(pointcut="bean(*ServiceImpl)",throwing = "e")
	public void doHandleException(JoinPoint jp,Exception e) {
		MethodSignature ms=(MethodSignature)jp.getSignature();
		log.error("{}'exception msg is {}",ms.getName(),e.getMessage());
	}
	
	监控:监控什么?
	1	cpu、磁盘、内存、网络
	2	Tomcat(线程)、JVM
	3	应用(service,...// 作业 将异常日志信息,写入到日志文件
	(扩展:播放音频,发送短信,发送email-java)

}

3.2 切入点表达式增强

Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明:
表-1 Spring AOP 中切入点表达式说明

指示符 作用
bean 用于匹配指定 bean 对象的 所有方法
within 用于匹配指定 下所有类内的 所有方法
execution 用于按指定语法规则 匹配到具体方法 (具体)
@annotation 用于匹配 指定注解修饰的方法 (具体)

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..*") 指定当前目录以及子目录中类的所有方法。

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..*.*(..)) - 万能配置。
    <mark>任意类型</mark> aop.service包<mark>任意子包</mark>的<mark>任意文件名</mark>。<mark>任意后缀</mark> (<mark>任意参数</mark>)

(..) - 任意参数

3.2.4 @annotation表达式(重点

@annotaion 表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析

  • @annotation(anno.RequiredLog) 匹配有此注解描述的方法。
  • @annotation(anno.RequiredCache) 匹配有此注解描述的方法。

其中:RequestLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行扩展操作。

课堂练习:

定义一 <mark>Cache相关切面</mark>

使用注解表达式定义切入点,并使用此注解对需要使用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 void doCache() {}
	 // @Around("doCache()")
			↑
			当方法比较多,上面这种方法比较方便	 

	  @Around("@annotation(com.cy.pj.common.annotation.RequiredCache)")  
	  public Object around(ProceedingJoinPoint jp)throws Throwable{
		  System.out.println("Get data from cache");
		  
		  Object obj = cacheMap.get(jp.getSignature().getName()); ← 获取缓存

			if(obj!=null)  ← 有缓存,直接返回
				return obj;

		  Object obj=jp.proceed();	← 没有缓存到这里... 这里从数据库获取数据
		  cacheMap.put(jp.getSignature().getName() , obj );  ← 数据加入缓存
		  System.out.println("Put data to cache");
		  return obj;
	}

												这里不用HashMap , 因为线程不安全
													↓
	private Map<<String,Object> cacheMap = new ConcurrentHashMap<>();

}

第三步:使用 @RequiredCache 注解对特定业务目标对象中的查询方法进行描述。

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

3.3 切面优先级设置实现

切面的优先级需要借助 @Order 注解进行描述,<mark>数字越小优先级越高</mark>,默认优先级比较低。

例如:
定义日志切面并指定优先级。

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

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

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

说明:
当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过滤器链、***链

其执行分析如图-9所示:


图-9

3.4 关键对象与术语总结

Spring 基于AspectJ框架实现AOP设计的关键对象概览,如图-10所示:


图-10

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

本小节作为课堂练习,以 AOP 方式记录项目中的用户行为信息,并将其存储到数据库。

package com.edut.springboot.tarena.common.aspect;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.edut.springboot.tarena.common.annotation.RequiredLog;
import com.edut.springboot.tarena.dao.SysLogDao;
import com.edut.springboot.tarena.pojo.SysLog;
import com.edut.springboot.tarena.service.SysLogService;

import lombok.extern.slf4j.Slf4j;

/** * @Aspect 注解描述的类型为一个 AOP 应用中的切面类型,此类的构成: * 1)切入点 (在哪些方法执行时,我们要植入扩展功能):例如所有地铁站的入口 * 2)通知 (要植入的扩展功能):例如安检 * * @author Administrator * */
@Aspect
@Component
@Slf4j
public class SysLogAspect {

	@Autowired
	private SysLogService sysLogService ;
	
	//sysLogServiceImpl
	@Pointcut("bean(sysUserServiceImpl)")
	public void joinPoint() {} ; 
	
	@Around("joinPoint()")
	public Object around(ProceedingJoinPoint pj ) throws Throwable {
		try {
			
			long start = System.currentTimeMillis(); 
			Object result = pj.proceed();
			long  end = System.currentTimeMillis() ; 
			
			//写正常日志
			saveLog(pj , end-start) ; 
			
			return result ;
		} catch (Throwable e) {
			
			//写异常日志
			
			log.error(e.getMessage());
			throw e; 
		}
	}
	
	/*将用户行为信息写入到数据库 saveLog */
	private void saveLog(ProceedingJoinPoint pj, long time) throws NoSuchMethodException, SecurityException {
		
		// 1.获取行为日志(借助 连接点 对象 - JoinPoint)
		MethodSignature signature =(MethodSignature) pj.getSignature(); // (MethodSignature) : 提供了参数的 字符串到类型的转换
		Class<?> clazz = pj.getTarget().getClass();
		
		String ip =  "192.168.0.1";
		String username = "GBK1910";
		//1.3 获取目标方法实际参数
		String params = Arrays.toString(pj.getArgs());
		//1.4 获取操作名称(由此注解RequiredLog指定)
		//1.4.1 获取目标方法
		Method method = clazz.getDeclaredMethod(signature.getName() , signature.getParameterTypes()); 
		String methodName =clazz.getName() +"."+ method.getName(); 
		
		/* * 老师加了判断。。。。 */
		String operation ="operation" ; 
		RequiredLog annotation = method.getAnnotation(RequiredLog.class);
		if(annotation!=null) {
			operation = annotation.operation() ; 
		}	
		
		//2. 封装为日志
		SysLog entity = new SysLog()
				.setIp(ip)
				.setUsername(username)  
				.setMethod(methodName)
				.setParams(params)
				.setOperation(operation)
				.setTime(time); 
		//3.写日志
		sysLogService.saveObject(entity);
	}
	
}



文章目录


4 Spring AOP事务处理

4.1 Spring 中事务简介

4.1.1 事务定义

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

4.1.2 事务特性

事务具备ACID特性,分别是:

  • 原子性(Atomicity):一个事务中的<mark>多个操作</mark>要么都成功要么都失败。
  • 一致性(Consistency):(例如存钱操作,存之前和存之后的<mark>总钱数</mark>应该是一致的。
  • 隔离性(Isolation):<mark>事务与事务</mark>应该是相互隔离的。
  • 持久性(Durability):事务一旦提交,数据要持久<mark>保存</mark>。

说明:
目前市场上在事务一致性方面,通常会做一定的优化,
比方说只要<mark>最终一致</mark>就可以了,这样的事务我们通常会称之为<mark>柔性事务</mark>(只要最终一致就可以了).

4.1.3 案例分析

现有两个订单业务操作,在下单时需要执行更新库存。
当库存充足时

如图-11所示:


图-11

当库存不足时,如图12所示:


图-12

4.2 Spring 中事务管理 - @Transactional

4.2.1 Spring - 事务 - 概述

<mark>Spring 提供了两种事务管理方式</mark>:

  • 编程式事务
  • 声明式事务
  • <mark>编程式</mark>事务指的是 <mark>通过编码方式实现事务</mark>; (显式声明/关闭事务)
  • 声明式事务 基于 AOP,将具体业务逻辑与事务处理 解耦

<mark>通过声明式事务管理可以更好使业务代码逻辑不受污染或少量污染,
因此在实际使用中声明式事务用的比较多</mark>。

Spring 声明式事务管理将开发者从繁复的事务管理代码中解脱出来,专注于业务逻辑的开发上,
这是一件可以被拿来顶礼膜拜的事情。

从具体配置实现上,Spring框架提供了两种配置方式

  • 一种是基于 xml 方式做配置实现,
  • 另一种是基于 @Transactional 注解进行配置实现。

SpringBoot 项目,其内部提供了事务的自动配置机制,
当我们使用 spring-boot-starter-jdbcspring-boot-starter-data-jpa 依赖的时候,
框架会自动为我们的项目注入 DataSourceTransactionManagerJpaTransactionManager

这两个事务管理器都实现了Spring中提供的 PlatformTransactionManager 接口,它是Spring的事务核心接口。

4.2.2 Spring - 事务 - 实现 - @Transactional

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

基于 @Transactional 注解进行声明式事务管理的实现步骤分为两步

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


	==  其 【业务类中】 代码示例如下: ==



@Transactional(timeout = 30,
               readOnly = false,
               isolation = Isolation.READ_COMMITTED,
               rollbackFor = Throwable.class,
               propagation = Propagation.REQUIRED)
@Service 
public class SysUserServiceImpl implements SysUserService {
	@Transactional(readOnly = true)
	@Override
	public PageObject<SysUserDeptVo> findPageObjects(
			String username, Integer pageCurrent) {}
}

<mark>其中,代码中的@Transactional注解用于描述类或方法,告诉spring框架我们要在此类的方法执行时进行事务控制</mark>

其具体说明如下:

  • @Transactional 注解应用在类上时表示类中所有方法启动事务管理,并且一般用于事务共性的定义。
  • @Transactional 描述方法时表示此方法要进行事务管理,假如类和方法上都有@Transactional注解,则方法上的注解一般用于事务特性的定义。

[ @Transactional 常用属性应用说明 ]:

  • timeout 事务的超时时间,<mark>默认值为-1,表示没有超时显示</mark>。
    如果配置了具体时间,则超过该时间限制但事务还没有完成,则自动回滚事务。
  • read-only 指定事务<mark>是否为只读事务</mark>,<mark>默认值为 false</mark>;
    为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
  • rollback-for 用于指定能够触发事务<mark>回滚的异常类型</mark>
    如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
  • no-rollback- for 抛出 no-rollback-for 指定的异常类型,<mark>不回滚事务</mark>。
  • isolation 事务的隔离级别,默认值采用 DEFAULT。 建议READ_COMMITTED (可以避免脏读)

可重复读- 锁行
可序列化 - 锁表

Spring 中事务控制过程分析,如图-13所示:
图-13

  • 当方法上面有注解描述时候,自动统一开启 事务和提交
  • 当放在类上面,类中所有方法都 是事务

4.2.3 Spring 中 事务传播(增殖)特性

事务传播( Propagation )特性指"不同业务(service)对象"中的事务方法之间相互调用时,

事务的传播方式,如图-14所示:


图-14

.


其中,常用事务传播方式如下: ( 默认

  • @Transactional(propagation=Propagation.REQUIRED)

如果没有事务创建新事务, 如果当前有事务参与当前事务,
<mark>Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况</mark>。

假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:
.
Service1#method1()Service2#method2()Service3#method3()
.
那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
.
如图-15所示:

图-15

代码示例如下:

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

.


  • @Transactional(propagation=Propagation.REQUIRES_NEW)
    必须是新事务, 如果有当前事务, 挂起当前事务并且开启新事务,如图-16所示:


图-16

代码示例如下:

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

4.3 Spring 中事务原理分析

Spring 事务管理是基于接口***(JDK)或动态字节码(CGLIB)技术,然后通过 AOP 实施事务增强的。

DataSourceTransactionManager 为例

  • DataSourceTransactionManager 中,在事务开始的时候,会调用 doBegin 方法,首先会得到相对应的 Connection
  • 然后可以根据事务设置的需要,对 Connection 的相关属性进行配置,比如将 ConnectionautoCommit 功能关闭等。

4.1 Spring 中事务管理 小结

<mark>Spring 声明式事务是 Spring 最核心</mark>

最常用的功能。

由于 Spring 通过 IOCAOP 的功能非常透明地实现了<mark>声明式事务</mark>的功能

对于一般的开发者基本上无须了解 Spring 声明式事务的内部细节,仅需要懂得如何配置就可以了。

但对于中高端开发者还需要了解其内部机制。


文章目录


5 Spring AOP 异步操作实现 - @Async

5.1 异步场景分析

在开发系统的过程中,通常会考虑到系统的性能问题,
提升系统性能的一个重要思想就是“串行”改“ 并行 ”。

说起“并行”自然离不开“异步”,今天我们就来聊聊如何使用 Spring@Async 的异步注解。

NIO

5.1 Spring 业务的异步实现 - @EnableAsync

5.1.1 启动异步配置

在基于注解方式的配置中,借助 @EnableAsync 注解进行异步启动声明

Spring Boot版的项目中,代码示例如下:

@EnableAsync <------- spring容器启动时会创建线程池 (系统底层会进行异步的自动配置)
@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}
#server
server:
  port: 80
  servlet:
    context-path: /
  tomcat:
    uri-encoding: utf-8
    max-threads: 1000
    min-spare-threads: 100

# spring
spring:
  aop:
    proxy-target-class: false # 
  task:
    execution:
      pool:
        core-size: 3 # 推荐 CPU核数+1
        max-size: 5 # 最大吞吐量 = max-size+queue-capacity . 超了报异常
        queue-capacity: 100 #默认100多个亿 INTEGER.MAX_VALUE
        keep-alive: 30 #30s后 释放 多余 core-size 的线程
        allow-core-thread-timeout: false #默认false 不释放core-size
        thread-name-prefix: spring.async.task- # 默认 task-
 

默认配置 ctrl+点击 ↑ ----- 在 TaskExecutionProperties 类中看
 

5.1.2 Spring中@Async注解应用

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


service中

	核心线程,最大线程,任务队列,线程名称,拒绝策略,任务执行时出现异常
	↓
	@Async  
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	@Override
	public void saveObject(SysLog entity) {
      System.out.println("SysLogServiceImpl.save:"+ Thread.currentThread().getName());
	  sysLogDao.insertObject(entity);
	  //try{Thread.sleep(5000);}catch(Exception e) {}
	}

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

	@Async
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	@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 对象可以对异步方法的执行结果进行封***r> 假如外界需要异步方法结果时,可以通过 Future 对象的 get 方法获取结果。

说明:
.
对于 @Async 注解默认会基于 ThreadPoolTaskExecutor 对象获取工作线程,
然后调用由 @Async 描述的方法,让方法运行于一个工作线程,以实现异步操作。



但是假如系统中的默认拒绝处理策略,
.
任务执行过程的异常处理不能满足我们自身业务需求的话,
.
我可以对异步线程池进行自定义.

( SpringBoot 中默认的异步配置可以参考自动配置对象 TaskExecutionAutoConfiguration ).

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

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

关键代码如下:

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=new ThreadFactory() {
		//CAS算法
		private AtomicInteger at=new AtomicInteger(1000);  //线程安全的 Integer
		@Override
		public Thread newThread(Runnable r) {
			return new Thread(r, "db-async-thread-"+at.getAndIncrement());
		}
	};	
	@Override
    public 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("当前任务线程池队列已满.");
        });
// 上面 lambda 表达式 等于 ↓
// executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
// 
// @Override
// public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// log.warn("当前任务线程池队列已满");
// }
// });


        executor.initialize();
        return executor;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(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.0-1 JSR107


自动配置原理

执行流程

name 匹配 - 》 key 生成 - 原则

6.0-2 Spring缓存抽象 - 注解



key 表达式

6.1 缓存场景分析

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

6.2 Spring 中业务缓存应用实现

6.2.1 启动缓存配置 - @EnableCaching

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

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

}

6.2.2 业务方法上应用缓存配置 - @Cacheable - @CacheEvict

  • 在需要进行缓存的业务方法上通过 @Cacheable 注解对方法进行相关描述.表示方法的
  • 返回值要存储到Cache中,假如在更新操作时需要将cache中的数据移除,可以在更新方法上使用 @CacheEvict 注解对方法进行描述。

例如:

第一步:在用户模块查询相关业务方法中,使用缓存,关键代码如下:


将查询到的数据存储到cache 中,
value值menuCache为具体cache对象 - [别名:cacheNames]@Cacheable(value = "userCache") SinpleKey存储实际参数的值,将这些值的组合作为key  <-------- value 指定 cache 的名称
@Transactional(readOnly = true)
@Override
public Map<String,Object> findObjectById(Integer id) {


问题1:menuCache为谁? -> 方法的返回值
问题2:manuCache中存储数据时使用的key ?


....
}

其中
value 属性的值表示要使用的缓存对象,名字自己指定

其中底层为一个 map 对象
当向 cache 中添加数据时, key 默认为方法实际参数的组合。


第二步:在用户模块更新时,清除指定缓存数据,关键代码如下:

	@CacheEvict(	value="userCache",  <--- 清理的cache
					allEntries=true,    < ----- 清除所有元素
					beforeInvocation=false)  < ---- 在调用前 true /false      [invocation  /ˌɪnvəˈkeɪʃn 调用
	// @CacheEvict(value="userCache",key = "#entity.id") <------ key 指定元素
    @Override
    public int updateObject(SysUser entity, Integer[] roleIds) {}

其中,key表示要清除记录时,使用的具体key值是什么( #entity.id 表示按照参数对象中entity的id值进行缓存记录清除)。

说明:spring中的缓存应用原理,如下图所示:

如何查看上面的直观看到 - 上面 - 运行流程?
C:\Users\Administrator.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.2.2.RELEASE\spring-boot-autoconfigure-2.2.2.RELEASE.jar 中
spring.factories

指定 key



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架构的基本实现为例进行原理分析和说明,其简易架构如图-16所示:


图-16

其中 DefaultAdvisorAutoProxyCreator 这个类功能更为强大,这个类的奇妙之处是他实现了 BeanProcessor 接口,

ApplicationContext 读如所有的 Bean 配置信息后,这个类将扫描上下文,寻找所有的 Advistor

(一个 Advisor 是一个切入点和一个通知的组成),将这些 Advisor 应用到所有符合切入点的 Bean 中。

7.3 案例业务实现

7.3.1 业务描述

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

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

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

package com.cy.spring.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.spring.aop;
public interface SearchService {
	  Object search(String key);
}

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

package com.cy.spring.aop;
import org.springframework.stereotype.Service;
import com.cy.spring.annotation.RequiredLog;
@Service
public class DefaultSearchService implements SearchService {


	@RequiredLog  <----------- 注解
	@Override
	public Object search(String key) {
		System.out.println("search by "+key);
		return null;
	}
}

7.3.3 日志Advice对象定义

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

切面:类似 LogAspect 中的 @Around

package com.cy.spring.advisor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**Advice对象:用于为目标方法添加日志操作*/
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;
//Advisor
@Component	<------交给spring管理!!
public class LogAdvisor extends StaticMethodMatcherPointcutAdvisor {

	private static final long serialVersionUID = 7022316764822635205L;
	
	public LogMethodMatcher() {
		
	注册:在特定切入点上要执行的通知
				↓
		setAdvice(new LogAdvice());
	}
	
	//Pointcut
	//方法返回值为true时,则可以为目标方法对象创建***对象
	@Override
	public boolean matches(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 日志业务单元测试实现

基于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 void testSearch() {
		//System.out.println(searchService);
		searchService.search("tedu");
	}
}

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


文章目录

8 总结

8.1 重难点分析

AOP 是什么,解决了什么问题,实现原理,应用场景。
AOP 编程基本步骤及实现过程(以基于AspectJ框架实现为例)。
AOP 编程中的核心对象及应用关系。
AOP 思想在Spring中的实现原理分析。
AOP 编程中基于注解注解方式的配置实现。
AOP 编程中基于注解方式的事务控制。
AOP 编程中异步操作的实现?
AOP 编程中的缓存应用?

8.2 FAQ分析

  • 什么是 OCP 原则(开闭原则)
    对扩展开放
    对修改关闭 - (经常修改,可读性差,维护性差)
  • 什么是 DIP 原则 (依赖倒置)
    依赖于抽象
    而非具体(非实现类)
  • 什么是单一职责原则( SRP )
    类职责应该单一性,职责不能太多
  • 如何理解 AOP
    一种面向切面设计思想
    基于此思想,在对象运行时动态切入扩展功能
  • SpringAOP 的有哪些配置方式
    XML
    注解
  • Spring 中AOP 的通知有哪些基本类型
    (5种)
    1. 前置通知 (@Before) 。
    2. 返回通知 (@AfterReturning) 。
    3. 异常通知 (@AfterThrowing) 。
    4. 后置通知 (@After)。
    5. 环绕通知 (@Around) : <mstyle mathcolor="&#35;f01"> </mstyle> \color{#f01}{重点掌握} (<mark>优先级最高</mark>)
  • Spring 中 AOP 是如何为Bean对象创建***对象的 ?
    JDK
    CGLIB (Code Generator Library
  • Spring 中基于 AOP 编程时创建的***对象是何时创建
    容器初始化时 √
    业务执行时
  • SpringAOP 切面的执行顺序如何指定?
    借助 @Order 注解进行描述
  • Spring 中基于 AOP 实现的声明式事务控制原理是怎样的
  • Spring单体架构项目中事务的控制要通过Connection对象实现
  • Spring 如何保证一个线程一个Connection对象?借助ThreadLocal实现.?

8.3 Bug分析

切入点应用错误,如图-17所示:

图-17
问题分析:检查切入点的引入是否丢掉了"()".