AOP是面向切面编程,是一种设计思想,它要在不改变原有目标对象的基础上,为目标对象基于动态织入的特定方式进行功能扩展。这里的特定方式一种是编译时动态,还有一种是运行时动态。我们可以将设计思想理解为OOP(面向对象编程)思想的补充和完善,OOP强调的一种静态过程,而AOP是一种动态过程,它要为设计好的对象在动态编译或运行时做服务增益,例如记录日志、事务增强、权限控制等。AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们称之为动态代理对象。
创建代理对象的方式有两种:
1.借助JDK官方API为目标对象创建其兄弟类型,但是目标对象类型需要实现相应的接口
2.借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰
AOP相关术语
切面对象(Aspect):封装了扩展业务逻辑的对象,在spring中可以使用@Aspect描述。
切入点(Poincut):定义了切入扩展业务逻辑的一些方法的集合,就是哪些方法要运行的时候切入扩展业务,会通过表达式进行相关定义。一个切面中可以定义多个切入点的定义
连接点(JoinPoint):切入点方法集合中封装了某个正在执行的目标方法信息对象。其实就是连接点是切入点方法当中的用来封装这个方法信息的对象。可以通过此对象获取具体的目标方法信息,甚至区调用目标方法(执行扩展业务逻辑)
通知(Advice):切面(Aspect)内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知
AOP依赖
org.springframework.boot
spring-boot-starter-aop
2.5.2
package com.cy.pj.sys.service.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @Aspect注解描述的类型为切面对象类型,此切面中可以定义多个切入点和通知方法
*/
@Aspect
@Component
@Slf4j
public class SysLogAspect {
/**
* @Pointcut注解用于定义切入点 bean(" spring容器中bean的名字 ")这个bean表达式为切入点表达式定义的一种语法
* 它描述的是某个bean或多个bean中所有方法的集合为切入点;缺点是不能精确到某一个具体方法
*/
@Pointcut("bean(sysUserServiceImpl)")//这个bean中所有方法的集合都是切入点
//@Pointcut("bean(*ServiceImpl)")表示名字以ServiceImpl结尾的所有bean
public void doLog() {//此方法用来承载切入点
}
/**
* @param joinPoint 连接点对象,此对象封装了要执行的切入点方法信息,
* 可以通过连接点对象调用目标方法
* @return 目标方法的执行结果
* @throws Throwable
* @Around()注解描述的方法,会在切入点执行之前和之后执行业务拓展, 在当前业务中,此方法为日志环绕通知方法
*/
@Around("doLog()")//在这个切入点方法执行的时候,执行下面这个通知方法
public Object doAround(ProceedingJoinPoint joinPoint/*这个连接点只能用在环绕通知中*/) throws Throwable {
log.info("start:{}" + System.currentTimeMillis());
try {
Object result = joinPoint.proceed();//执行目标方法(切入点中的某个方法,就是正在执行的方法)
log.info("end:{}" + System.currentTimeMillis());
return result;
} catch (Throwable e) {
e.printStackTrace();
log.error("Exception:{}" + System.currentTimeMillis());
throw e;
}
}
}
Spring切面工作原理
当我们切面内部切入点对应的目标业务方法执行时,底层会通过代理对象用反射来访问切面中的通知方法,进而通过通知方法为目标业务做功能增强。我们可以通过断点看到,如图:
如果两个方法用了同一个切入点,执行顺序是随机的,要看spring容器优先扫描到哪个。
JDK动态代理
要在yml配置文件中加上
spring:
aop:
proxy-target-class: false
Spring中AOP通知类型
@Around(所有通知中优先级最高的通知,可以在执行方法之前和之后灵活进行业务拓展)
@Before(在目标方法之前调用)
@AfterReturning(目标方法正常结束时执行)
@AfterThrowing(目标方法异常结束时执行)
@After(目标方法正常结束和目标方法异常结束时都执行)
package com.cy.pj.sys.service.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//ProceedingJoinPoint joinPoint这个连接点只能用在环绕通知中
//如果其它方法想用连接点,可以用JoinPoint jp
@Aspect
@Component
public class SysTimeAspect {
@Pointcut("bean(sysRoleServiceImpl)")
public void doTime() {
}
@Before("doTime()")
public void doBefore() {
System.out.println("@Before");
}
@After("doTime()")
public void doAfter() {
System.out.println("@After");
}
@AfterReturning("doTime()")
public void doAfterReturning() {
System.out.println("@AfterReturning");
}
@AfterThrowing("doTime()")
public void doAfterThrowing() {
System.out.println("@AfterThrowing");
}
//最重要,优先级最高
@Around("doTime()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
try {
System.out.println("@Around.before");
Object proceed = joinPoint.proceed();
System.out.println("@Around.AfterReturning");
return proceed;
} catch (Exception e) {
e.printStackTrace();
System.out.println("Around.AfterThrowing");
throw e;
} finally {
System.out.println("@Around.After");
}
}
}
Spring中AOP切入点表达式
在Spring工程中对于切入点表达式的定义,可以分成两大类型:
粗粒度切入点表达式定义(不能精确到具体方法)
细粒度切入点表达式定义(可以精确到具体方法)
粗粒度切入点表达式定义:
bean("bean的名字")表达式:用于粗粒度切入点的定义,不能精确到具体方法。
bean(sysUserServiceImpl)//这个bean中所有方法的集合都是切入点
bean(*ServiceImpl)//表示名字以ServiceImpl结尾的所有bean
within("包名..类型")表达式:用于粗粒度切入点的定义,不能精确到具体方法。
within(" com.cy.pj.sys.impl.SysUserServiceImpl")//表示这个包下这个类里的所有方法
within(" com.cy.pj.sys.impl.*")//表示这个包下所有类里的方法
within(" com.cy.pj.sys.impl..*")//表示这个包以及子包下所有类里的方法
细粒度切入点表达式定义
execution("返回值 类全名.方法名(参数列表)")表达式:用于细粒度切入点的定义,可以精确到具体方法。
execution("int com.cy.pj.sys.impl.SysUserServiceImpl.validById(Integer, Integer)//表示方法的返回值是int类型 这个包下的这个类中的这个方法,方法参数有两个Integer类型。
execution("* com.cy.pj.sys.impl.SysUserServiceImpl.*(..)//表示任意返回值,这个包这个类中的任意方法,参数列表也是任意。
execution("* com.cy.pj.sys.impl..*.*(..)//表示任意返回值,这个包以及子包下任意类中的任意方法,参数列表也是任意。
execution("* com.cy.pj.sys.impl.*.*(..)//表示任意返回值,这个包下任意类中的任意方法,参数列表也是任意。
@annotation("注解的类全名"):用于细粒度切入点的定义,可以精确到具体方法。
@annotation("com.cy.pj.annotation.RequiredCache")//表示由RequiredCache这个注解描述的方法就是缓存切入点方法
@annotation("com.cy.pj.annotation.RequiredLog")//表示由RequiredLog这个注解描述的方法就是日志切入点方法
Spring中切面优先级设置
当项目中有多个切面时,且对应着相同的切入点,假如切面中的通知方法的执行需要有严格顺序要求,此时我们需要设置切面的优先级,这个优先级可以通过@Order(数字)注解进行描述,数字越小优先级越高。假如没有指定优先级,默认是最低的优先级。相同切入点下的多个切面会形成一个切面链。
Spring中异步操作
1.首先用@EnableAsync注解描述启动类,在我们再次运行启动类的时候,底层会帮我们配置一个线程池(这个底层配置的线程池,核心线程数默认是8)。
2.用@Async注解描述方法,在spring中会认为这个是异步切入点方法,在这个切入点方法进行时,底层会通过通知方法获取线程池中的线程,来调用切入点方法。但是这个注解不能直接去描述有方法返回值的方法,因为我们并不知道异步操作底层到底是何时结束
Spring中yml文件中线程池的配置
task:
execution:
pool:
core-size: 3
max-size: 5
keep-alive: 60000
queue-capacity: 10
thread-name-prefix: db-service-task-
core-size:核心线程数 一般设置的值是cpu核数+1 有IO操作的设置2xcpu核数x8+1 1是指磁盘的个数
max-size:最大线程数(包含核心线程)
keep-alive:空闲等待时间
queue-capacity:队列容量,也就是等待排队的线程数
thread-name-prefix:设置线程名的前缀,用于好分辨线程是哪来的
core-size: 3 当核心线程数没有达到3时,每来一个新的任务就会创建一个新的线程,然后存储到线程池中。假如池中线程数已经达到核心线程数时,再接收新的任务时,要检查是否有空闲的核心线程,假如有,则使用空闲的核心线程执行新的任务。
queue-capacity: 假如核心线程数已经达到核心线程数的值,并且所有核心线程都在忙,再来新任务时,会将任务存储到任务队列中。
max-size: 当队列已满,核心线程数也都在忙,再来新的任务则会创建新的线程,但所有的线程数不能超过最大线程数设置的值,否则会抛出异常,拒绝执行。
keep-alive: 假如池中的线程数多余核心线程数的值,此时又没有新的任务,则一旦空闲线程空闲的时间超过keep-alive设置的时间值,则会被释放。
这些参数值都是传给ThreadPoolExecutor这个对象。
Spring中事务的处理
Spring中事务控制,推荐在业务层基于AOP方式进行实现,这样可以将事务逻辑与业务逻辑进行更好的解耦,同时重用事务逻辑代码。
@Transactional注解描述的方法为事务切入点方法,此方法在执行时就会通过通知方法为其进行事务的增强
@Transactional注解内部属性:
1.readyOnly 用来描述此事务是否为只读事务,若为只读,readOnly为true,默认值是false(表示不是只读事务),对于查询而言建议设置readOnly为true。
2.rollbackFor 用来指定,出现什么异常的时候回滚(默认RuntimeException)
3.noRollbackFor 用来指定出现什么异常时,不回滚
4.isolation用来设置事务并发执行隔离级别(隔离级别越高,数据正确性约好,但并发越差)
4.1 Isolation.READ_COMMITTED只读取别人提交的数据
4.2 Isolation.REPEATABLE_READ读数据的时候不给别人修改,重复读
4.3 Isolation.SERIALIZABLE在统计的时候不允许写入或删除数据
5.timeout超时时间 如果事务执行时间超过了这个设置的值,则抛出异常。默认-1(不超时,实际项目中建议设置超时时间)
6.propagation设置事务传播特性(默认值为Propagation.REQUIRED),不同业务对象之间的方法出现相互调用时,事务的执行策略。REQUIRED表示参与调用者的事务中去。
6.1Propagation.REQUIRED表示不管谁来调用,都参与到调用的事务中去
6.2Propagation.REQUIRES_NEW表示不管谁来调用,都不管,只在自己的事务中
如果类上和方法上都定义了事务的特性,那么方法的优先级最高