面向切面AOP
大约 24 分钟
Aop
AOP 产生动机
动态代理设计模式
- 在一个完善的项目开发过程之中,为了便于对某些公共业务处理逻辑的抽象(例如:数据库事务处理操作),可以使用动态代理模式来完成,如图所示。此时业务调用处理类需要通过 ServiceProxy 获取封装的业务接口实例,而后基于反射调用的形式在核心业务方法调用的前后采用硬编码的方式来实现代理调用。
1、
package com.yootk.vo;
public class Dept {
private Long deptno;
private String dname;
private String loc;
public Long getDeptno() {
return deptno;
}
public void setDeptno(Long deptno) {
this.deptno = deptno;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
}
2、
package com.yootk.service;
import com.yootk.vo.Dept;
public interface IDeptService { // 定义一个业务接口
public boolean add(Dept dept); // 数据增加
}
3、
package com.yootk.service.impl;
import com.yootk.service.IDeptService;
import com.yootk.vo.Dept;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DeptServiceImpl implements IDeptService {
private static final Logger LOGGER = LoggerFactory.getLogger(DeptServiceImpl.class);
// 此时需要注入DAO接口实例,才可以实现数据层代码的开发处理
@Override
public boolean add(Dept dept) {
LOGGER.info("【部门增加】部门编号:{}、部门名称:{}、部门位置:{}",
dept.getDeptno(), dept.getDname(), dept.getLoc());
return true;
}
}
4、
package com.yootk.service.proxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ServiceProxy implements InvocationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceProxy.class);
private Object target; // 保存真实对象
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 需要判断当前的操作方法之中是否需要进行代理控制
if (this.isOpenTransaction(method)) {
LOGGER.info("【JDBC事务】当前的业务方法需要进行事务的开启...");
}
Object result = method.invoke(this.target, args); // 方法调用
if (this.isOpenTransaction(method)) {
LOGGER.info("【JDBC事务】对于当前的业务调用要进行事务的提交或者是回滚处理...");
}
return result;
}
private boolean isOpenTransaction(Method method) {
// 如果这个逻辑需要修改,或者是要扩充更多的处理方法呢?
if (method.getName().startsWith("add")) { // 这样的判断逻辑要写多少个呢?
return true; // 现在需要开启事务
}
return false; // 不需要开启事务
}
}
5、
package com.yootk.factory;
import com.yootk.service.proxy.ServiceProxy;
public class ServiceFactory {
private ServiceFactory() {};
public static <T> T getInstance(Class<T> clazz) throws Exception {
Object target = clazz.getConstructors()[0].newInstance(); // 对象实例化
return (T) new ServiceProxy().bind(target); // 绑定对象
}
}
6、
package com.yootk.main;
import com.yootk.factory.ServiceFactory;
import com.yootk.service.IDeptService;
import com.yootk.service.impl.DeptServiceImpl;
import com.yootk.vo.Dept;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StartService {
private static final Logger LOGGER = LoggerFactory.getLogger(StartService.class);
public static void main(String[] args) throws Exception {
IDeptService deptService = ServiceFactory.getInstance(DeptServiceImpl.class);
Dept dept = new Dept();
dept.setDeptno(10L);
dept.setDname("教学研发部");
dept.setLoc("北京");
LOGGER.info("【部门数据增加】{}", deptService.add(dept)); // 业务调用
}
}
AOP 简介
AOP 核心概念
AOP 本质上属于一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP)模型。AOP 是基于动态代理的一种更高级应用,其主要的目的是可以结合 AspectJ 组件利用切面表达式将代理类织入到程序组成之中,以实现组件的解耦合设计。AOP 主要用于横切关注点分离和织入,因此在 AOP 的处理之中需要关注于如下几个核心概念
- 关注点:可以认为是所关注的任何东西,例如:业务接口、支付处理、消息发送处理等;
- 关注点分离:将业务处理逐步拆分后形成的一个个不可拆分的独立组件;
- 横切关注点:实现代理功能,利用代理功能可以将辅助操作在多个关注点上执行,横切点可能包括很多种:事务处理、日志记录、角色或权限检测、性能统计等;
- 织入:将横切关注点分离之后有可能需要确定关注点的执行位置,可能在业务方法调用前,也可能是调用之后或者是产生异常时。
AOP 通知处理
- 在整个 AOP 设计过程之中,横切关注点的设置与处理是核心关键所在,其中横切点需要采用横切点表达式进行定义,而处理的核心就是具体的代理类。在 AOP 设计之中,可以采用如下通知(Advice)处理形式来实现不同横切点的配置:
- 前置通知处理(Before Advice):在真正的核心功能调用之前触发代理操作;
- 后置通知(After Advice):真正的核心功能调用之后进行触发;
- 后置返回通知(After Returning Advice):当执行的核心方法返回数据的时候处理
- 后置异常通知(After Returning Advice):当执行真实处理方法产生异常之后通知
- 后置最终通知(After Finally Advice):不管是否出现问题都执行此操作;
- 环绕通知(Round Advice):环绕通知可以在方法调用之前和之后自定义任何行为(包括前置与后置通知处理),并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等;
1、
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 需要判断当前的操作方法之中是否需要进行代理控制
if (this.isOpenTransaction(method)) {
LOGGER.info("【JDBC事务】当前的业务方法需要进行事务的开启...");
}
Object result = null;
try {
result = method.invoke(this.target, args); // 方法调用
} catch (Exception e) {
// 异常出现的时候再执行某些的处理操作。
}
if (this.isOpenTransaction(method)) {
LOGGER.info("【JDBC事务】对于当前的业务调用要进行事务的提交或者是回滚处理...");
}
return result;
}
AOP 切入点表达式
切入点标识
- 在 AOP 执行过程之中切入点的处理是最关键的核心步骤,如果切入点配置错误那么所有的通知处理方法将无法进行正常的织入,Spring AOP 支持的主要的 AspectJ 切入点标识符如下:
- execution:定义通知的切入点;
- this:用于匹配当前 AOP 代理对象类型的执行方法,
- target:用于匹配当前目标对象类型的执行方法:
- args:用于匹配当前执行的方法传入的参数为指定类型的执行方法:
切入点表达式
- execution(注解匹配?修饰符匹配?方法返回值类型 操作类型匹配 方法名称匹配(参数匹配))异常匹配?
- 【可选】注解匹配:匹配方法上指定注解的定义,例如:@Override;
- 【可选】修饰符匹配:方法修饰符,可以使用 public 或 protected;
- 【必填】方法返回值类型:可以设置任何类型,可以使用“*”匹配所有返回值类型:
- 【必填】操作类型:定义方法所在类的名称,可以使用"*”匹配所有类型;
- 【必填】方法名称匹配:匹配要调用的处理方法,使用“*”匹配所有方法
- 【必填】参数匹配:用于匹配切入方法的参数,有如下几种设计方式:
- ():表示没有参数;
- (..):表示匹配所有参数;
- (..,java.lang.String):以 String 作为最后一个参数,前面的参数个数可以任意:
- (java.lang.String,..):以 String 作为第一个参数,后面的参数个数可以任意:
- (*.java.lang.String):以 String 作为最后一个参数,前面可以随意设置一个参数。
- 【可选】异常匹配:定义方法名称中 throws 抛出的异常,可以设置多个,使用“,”分割。
1、
com.yootk
· com.yootk.erp.service;
· com.yootk.crp.service;
2、
com.yootk..service
3、
execution(public * com.yootk..service..*.*(..))
AOP 基础实现
AOP 基础开发
- Spring 针对于 AOP 的实现提供了“spring-aop”以及“spring-aspects”两个依赖库开发者只需要在项目中配置这两个依赖库,就可以自定义 AOP 的处理类,以实现业务的代理操作,下面将依据图所示的结构实现一个 AOP 具体应用。
1、
// https://mvnrepository.com/artifact/org.springframework/spring-aop
implementation group: 'org.springframework', name: 'spring-aop', version: '5.3.20'
// https://mvnrepository.com/artifact/org.springframework/spring-aspects
implementation group: 'org.springframework', name: 'spring-aspects', version: '5.3.20'
2、
package com.yootk.service;
public interface IMessageService {
public String echo(String msg);
}
3、
package com.yootk.service.impl;
import com.yootk.service.IMessageService;
import org.springframework.stereotype.Service;
@Service // 扫描注册
public class MessageServiceImpl implements IMessageService {
@Override
public String echo(String msg) {
return "【ECHO】" + msg;
}
}
4、
package com.yootk.service.advice;
import org.aopalliance.aop.Advice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ServiceAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAdvice.class);
public void beforeHandle() {
LOGGER.info("启用业务功能前置调用处理机制。");
}
public void afterHandle() {
LOGGER.info("启用业务功能后置调用处理机制。");
}
}
5、
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:annotation-config/> <!-- 启用上下文注解的方式进行配置 -->
<context:component-scan base-package="com.yootk.service"/> <!-- 配置扫描包 -->
<bean id="serviceAdvice" class="com.yootk.service.advice.ServiceAdvice"/> <!-- AOP切面处理类 -->
<aop:config> <!-- AOP的配置核心 -->
<aop:pointcut id="messagePointcut"
expression="execution(public * com.yootk..service..*.*(..))"/>
<aop:aspect ref="serviceAdvice"> <!-- 当前进行AOP处理的切面操作类 -->
<aop:before method="beforeHandle" pointcut-ref="messagePointcut"/> <!-- 前置通知处理 -->
<aop:after method="afterHandle" pointcut-ref="messagePointcut"/> <!-- 后置通知处理 -->
</aop:aspect>
</aop:config>
</beans>
6、
package com.yootk.test;
import com.yootk.service.IMessageService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ContextConfiguration(locations = {"classpath:spring/spring-base.xml"})
@ExtendWith(SpringExtension.class)
public class TestMessageService {
private static final Logger LOGGER = LoggerFactory.getLogger(TestMessageService.class);
@Autowired
private IMessageService messageService;
@Test
public void testEcho() {
LOGGER.info("【ECHO调用】{}" + this.messageService.echo("沐言科技:www.yootk.com"));
}
}
AOP 代理实现模式
设置代理模式
- 动态代理设计模式在实际项目的运行过程中存在有两种实现机制,一种是基于 JDK 原生实现,此种方式需要提供有统一的功能接口。另外一种是基于 CGLIB 库的实现,可以采用拦截器的模式实现代理控制,没有接口实现的强制性要求,而在 SpringAOP 开发之中由于需要考虑到各种可能存在的应用环境,针对于这两种代理模式都有支持,开发者只需要通过<aop:config>元素中的 proxy-target-class 属性即可配置,该配置项有两种取值:
- “<aop:config proxy-target-class="true">”:使用 CGLIB 库实现代理织入;
- “<aop:config proxy-target-class="false">”:默认配置,使用 JDK 动态代理机制实现织入。
1、
<aop:config proxy-target-class="false"> <!-- AOP的配置核心 -->
<aop:pointcut id="messagePointcut"
expression="execution(public * com.yootk..service..*.*(..))"/>
<aop:aspect ref="serviceAdvice"> <!-- 当前进行AOP处理的切面操作类 -->
<aop:before method="beforeHandle" pointcut-ref="messagePointcut"/> <!-- 前置通知处理 -->
<aop:after method="afterHandle" pointcut-ref="messagePointcut"/> <!-- 后置通知处理 -->
</aop:aspect>
</aop:config>
2、
package com.yootk.test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ContextConfiguration(locations = {"classpath:spring/spring-base.xml"})
@ExtendWith(SpringExtension.class)
public class TestApplicationContext {
private static final Logger LOGGER = LoggerFactory.getLogger(TestApplicationContext.class);
@Autowired
private ApplicationContext applicationContext; // 直接注入上下文实例
@Test
public void testEcho() {
LOGGER.info("【ApplicationContext实现类】{}", this.applicationContext.getClass().getName());
LOGGER.info("【IMessageService实现类】{}", this.applicationContext
.getBean("messageServiceImpl").getClass().getName());
}
}
通知参数接收
通知参数
- AOP 的代理控制类都会在每次业务方法调用时自动执行,但是每一个业务方法在执行时都可能需要进行业务参数的接收,所以如果需要代理方法中也可以接收到与之匹配的参数内容,那么就需要在代理方法以及切面表达式中进行执行参数的明确定义,如图所示,下面通过对原始代码的改造进行实现说明。
1、
package com.yootk.service.advice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ServiceAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAdvice.class);
public void beforeHandle(String msg) {
LOGGER.info("启用业务功能前置调用处理机制,方法参数:{}", msg);
}
public void afterHandle(String msg) {
LOGGER.info("启用业务功能后置调用处理机制,方法参数:{}", msg);
}
}
2、
<context:annotation-config/> <!-- 启用上下文注解的方式进行配置 -->
<context:component-scan base-package="com.yootk.service"/> <!-- 配置扫描包 -->
<bean id="serviceAdvice" class="com.yootk.service.advice.ServiceAdvice"/> <!-- AOP切面处理类 -->
<aop:config proxy-target-class="true"> <!-- AOP的配置核心 -->
<aop:pointcut id="messagePointcut"
expression="execution(public * com.yootk..service..*.*(..)) and args(msg)"/>
<aop:aspect ref="serviceAdvice"> <!-- 当前进行AOP处理的切面操作类 -->
<aop:before method="beforeHandle" pointcut-ref="messagePointcut"/> <!-- 前置通知处理 -->
<aop:after method="afterHandle" pointcut-ref="messagePointcut"/> <!-- 后置通知处理 -->
</aop:aspect>
</aop:config>
3、
<aop:config proxy-target-class="true"> <!-- AOP的配置核心 -->
<aop:pointcut id="messagePointcut"
expression="execution(public * com.yootk..service..*.*(..)) and args(msg)"/>
<aop:aspect ref="serviceAdvice"> <!-- 当前进行AOP处理的切面操作类 -->
<aop:before method="beforeHandle"
pointcut="execution(public * com.yootk..service..*.*(..)) and args(msg)"/> <!-- 前置通知处理 -->
<aop:after method="afterHandle"
pointcut="execution(public * com.yootk..service..*.*(..)) and args(msg)"/> <!-- 后置通知处理 -->
</aop:aspect>
</aop:config>
后置通知
后置通知处理
- AOP 代理操作的后置通知中一般会分为三种形式,分别为: 后置结束通知(<aop:after>元素定义)、后置返回通知以及异常处理通知。其中后置结束通知在任何时候都会触发,后置返回可以接收业务功能执行完成后的返回结果,而后置异常通知是在异常产生时才会触发,而异常有可能是在业务处理方法中产生,也有可能是在前置通知操作中产生。下面将对已有的 AOP 执行程序进行修改,以便于观察 AOP 中各个通知的调用。
1、
package com.yootk.service.impl;
import com.yootk.service.IMessageService;
import org.springframework.stereotype.Service;
@Service // 扫描注册
public class MessageServiceImpl implements IMessageService {
@Override
public String echo(String msg) {
if (!msg.contains("yootk")) { // 此时没有在字符串里面发现指定的内容
throw new RuntimeException("没有找到“yootk”的字符串,你不爱我了!");
}
return "【ECHO】" + msg;
}
}
2、
package com.yootk.service.advice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ServiceAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAdvice.class);
public void beforeHandle(String msg) {
LOGGER.info("启用业务功能前置调用处理机制,方法参数:{}", msg);
}
public void afterHandle(String msg) {
LOGGER.info("启用业务功能后置调用处理机制,方法参数:{}", msg);
}
public void afterReturningHandle(String msg) { // 后置返回业务通知
LOGGER.info("启用业务功能后置返回处理机制,方法参数:{}", msg);
}
public void afterThrowHandle(Exception e) { // 后置返回业务通知
LOGGER.info("启用业务功能后置异常处理机制,异常信息:{}", e.getMessage());
}
}
3、
<context:annotation-config/> <!-- 启用上下文注解的方式进行配置 -->
<context:component-scan base-package="com.yootk.service"/> <!-- 配置扫描包 -->
<bean id="serviceAdvice" class="com.yootk.service.advice.ServiceAdvice"/> <!-- AOP切面处理类 -->
<aop:config proxy-target-class="true"> <!-- AOP的配置核心 -->
<aop:pointcut id="messagePointcut"
expression="execution(public * com.yootk..service..*.*(..))"/>
<aop:aspect ref="serviceAdvice"> <!-- 当前进行AOP处理的切面操作类 -->
<aop:before method="beforeHandle" arg-names="msg"
pointcut="execution(public * com.yootk..service..*.*(..)) and args(msg)"/> <!-- 前置通知处理 -->
<aop:after method="afterHandle" arg-names="msg"
pointcut="execution(public * com.yootk..service..*.*(..)) and args(msg)"/> <!-- 后置通知处理 -->
<aop:after-returning method="afterReturningHandle" pointcut-ref="messagePointcut" returning="value" arg-names="value"/>
<aop:after-throwing method="afterThrowHandle" pointcut-ref="messagePointcut" throwing="e" arg-names="e"/>
</aop:aspect>
</aop:config>
4、
package com.yootk.test;
import com.yootk.service.IMessageService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ContextConfiguration(locations = {"classpath:spring/spring-base.xml"})
@ExtendWith(SpringExtension.class)
public class TestMessageService {
private static final Logger LOGGER = LoggerFactory.getLogger(TestMessageService.class);
@Autowired
private IMessageService messageService;
@Test
public void testEchoNormal() {
LOGGER.info("【ECHO调用】{}" + this.messageService.echo("沐言科技:www.yootk.com"));
}
@Test
public void testEchoException() {
LOGGER.info("【ECHO调用】{}" + this.messageService.echo("李兴华编程训练营"));
}
}
AOP 环绕通知开发与配置
AOP 的切面处理程序设计时,充分考虑到了代码的解耦和设计问题,所以针对于不同的通知给出了不同的配置元素定义,但是这样的操作逻辑过于琐碎,所以又提供一种环绕通知的处理模型,这种模型类似于传统的动态代理结构,开发者可以根据自己的需要定义各类的通知结构
1、
package com.yootk.service.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
public class ServiceAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAdvice.class);
// 按照正常的设计来讲,如果业务层的方法出现了异常,应该是交给调用处进行处理的
public Object handleRound(ProceedingJoinPoint point) throws Throwable { // 环绕通知
LOGGER.debug("【环绕通知处理】目标对象:{}", point.getTarget());
LOGGER.debug("【环绕通知处理】对象类型:{}", point.getKind());
LOGGER.debug("【环绕通知处理】切面表达式:{}", point.getStaticPart());
LOGGER.debug("【环绕通知处理】方法签名:{}", point.getSignature());
LOGGER.debug("【环绕通知处理】源代码定位:{}", point.getSourceLocation());
LOGGER.info("【通知前置处理】业务方法调用前,传递的参数内容:{}", Arrays.toString(point.getArgs()));
Object returnValue = null; // 接收方法的返回值
try {
returnValue = point.proceed(point.getArgs()); // 调用真实业务主题
} catch (Exception e) {
LOGGER.info("【异常通知】业务方法产生异常,异常信息:{}", e.getMessage());
}
LOGGER.info("【返回通知】业务方法执行完毕,返回方法处理结果:{}", returnValue);
return returnValue;
}
}
2、
<context:annotation-config/> <!-- 启用上下文注解的方式进行配置 -->
<context:component-scan base-package="com.yootk.service"/> <!-- 配置扫描包 -->
<bean id="serviceAdvice" class="com.yootk.service.advice.ServiceAdvice"/> <!-- AOP切面处理类 -->
<aop:config proxy-target-class="true"> <!-- AOP的配置核心 -->
<aop:pointcut id="messagePointcut"
expression="execution(public * com.yootk..service..*.*(..))"/>
<aop:aspect ref="serviceAdvice"> <!-- 当前进行AOP处理的切面操作类 -->
<aop:around method="handleRound" pointcut-ref="messagePointcut"/>
</aop:aspect>
</aop:config>
基于 Annotation 实现 AOP 配置
基于 XML 实现的 AOP 开发
使用 XML 进行 AOP 配置实现过程之中都需要先定义配置类,而后再进行 XML 配置文又必须考虑到方法名称的一致性,这样在进行件的定义,而在进行 XML 配置定义时,代码设计与维护的过程中就会非常的繁琐
AOP 注解
- @Aspect -定义 AOP 处理类,该类必须为已注册的 SpringBean 实例
- @Pointcut -AOP 切面表达式
- @Before -AOP 前置通知处理方法
- @After -AOP 后置通知处理方法
- @AfterReturning -AOP 后置返回通知处理方法
- @AfterThrowing -AOP 后置异常通知处理方法
- @Around -AOP 环绕通知
1、
<context:annotation-config/> <!-- 启用上下文注解的方式进行配置 -->
<context:component-scan base-package="com.yootk.service"/> <!-- 配置扫描包 -->
<aop:aspectj-autoproxy proxy-target-class="false"/>
2、
package com.yootk.service.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@Aspect // 需要明确的告诉Spring容器,当前是一个AOP程序类
@Configuration // 此时进行的是AOP的代理配置
public class ServiceAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAdvice.class);
@Before(argNames = "msg",
value = "execution(public * com.yootk..service..*.*(..)) and args(msg)")
public void beforeHandle(String msg) {
LOGGER.info("启用业务功能前置调用处理机制,方法参数:{}", msg);
if (!msg.contains("yootk")) { // 在前置通知中触发检查
throw new RuntimeException("没有发现“yootk”标记,为了防止我家宝宝发飙,赶紧跑路!");
}
}
@After(argNames = "msg",
value = "execution(public * com.yootk..service..*.*(..)) and args(msg)")
public void afterHandle(String msg) {
LOGGER.info("启用业务功能后置调用处理机制,方法参数:{}", msg);
}
// 现在为止所有的切面都是在具体的通知上配置的,我们如果要想配置公共的切面也是可以的
@Pointcut("execution(public * com.yootk..service..*.*(..))")
public void pointcut() {} // 需要定义有一个方法
@AfterReturning(pointcut = "pointcut()", argNames = "msg", returning = "msg")
public void afterReturningHandle(String msg) { // 后置返回业务通知
LOGGER.info("启用业务功能后置返回处理机制,方法参数:{}", msg);
}
@AfterThrowing(pointcut = "pointcut()", throwing = "e", argNames = "e")
public void afterThrowHandle(Exception e) { // 后置返回业务通知
LOGGER.info("启用业务功能后置异常处理机制,异常信息:{}", e.getMessage());
}
@Around("pointcut()")
// 按照正常的设计来讲,如果业务层的方法出现了异常,应该是交给调用处进行处理的
public Object handleRound(ProceedingJoinPoint point) throws Throwable { // 环绕通知
LOGGER.info("【Around - 通知前置处理】业务方法调用前,传递的参数内容:{}", Arrays.toString(point.getArgs()));
Object returnValue = null; // 接收方法的返回值
try {
returnValue = point.proceed(point.getArgs()); // 调用真实业务主题
} catch (Exception e) {
LOGGER.info("【Around - 异常通知】业务方法产生异常,异常信息:{}", e.getMessage());
throw e; // 将产生的异常向上抛出
}
LOGGER.info("【Around - 返回通知】业务方法执行完毕,返回方法处理结果:{}", returnValue);
return returnValue;
}
}
AOP 注解启用
基于注解方式启用 AOP
- 此时的 AOP 相关配置项都是基于配置文件编写的,而在现代的 Spring 开发技术中已经不推荐使用 XML 配置模式了。为了解决这类的设计需要,在 Spring 中提供了一个'@EnableAspectJAutoProxy”注解,开发者可以利用该注解实现 AOP 的启用配置
1、
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:annotation-config/> <!-- 启用上下文注解的方式进行配置 -->
<context:component-scan base-package="com.yootk.service"/> <!-- 配置扫描包 -->
<aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>
2、
package com.yootk.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy // 启用AOP配置环境
@ComponentScan("com.yootk.service") // 定义扫描包
public class AOPConfig {
}
3、
package com.yootk.main;
import com.yootk.config.AOPConfig;
import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class StartApplicationContext { // 程序启动类
private static final Logger LOGGER = LoggerFactory.getLogger(StartApplicationContext.class);
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AOPConfig.class); // 构造方法定义配置类
IMessageService messageService = context.getBean(IMessageService.class); // 获取业务实例
LOGGER.info(messageService.echo("沐言科技:www.yootk.com")); // 业务调用
}
}
4、
package com.yootk.test;
import com.yootk.config.AOPConfig;
import com.yootk.service.IMessageService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ContextConfiguration(classes = {AOPConfig.class}) // 定义启动类
@ExtendWith(SpringExtension.class)
public class TestMessageService {
private static final Logger LOGGER = LoggerFactory.getLogger(TestMessageService.class);
@Autowired
private IMessageService messageService;
@Test
public void testEchoNormal() {
LOGGER.info("【ECHO调用】{}" + this.messageService.echo("沐言科技:www.yootk.com"));
}
@Test
public void testEchoException() {
LOGGER.info("【ECHO调用】{}" + this.messageService.echo("李兴华编程训练营"));
}
}
@EnableAspectJAutoProxy 注解
@EnableAspectJAutoProxy 注解
- @EnableAspectJAutoProxy 注解是基于配置类的 AOP 启用核心,由于 Spring 中的 AOP 实现方式存在有 JDK 代理与 CGLIB 代理两种形式,所以在该注解的内部也提供有相应的配置属性:
- proxyTargetClass:默认为 false,表示使用 JDK 代理实现。设置为 true 表示启用 CGLIB 代理实现:
- exposeProxy:默认为 false,表示不对外暴露代理类实例。设置为 true 表示对外暴露代理类实例。
1、
package com.yootk.service.advice;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.context.annotation.Configuration;
@Aspect // 需要明确的告诉Spring容器,当前是一个AOP程序类
@Configuration // 此时进行的是AOP的代理配置
public class ServiceAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAdvice.class);
@Pointcut("execution(public * com.yootk..service..*.*(..))")
public void pointcut() {} // 需要定义有一个方法
@Around("pointcut()")
public Object handleRound(ProceedingJoinPoint point) throws Throwable { // 环绕通知
LOGGER.info("{}", AopContext.currentProxy()); // 获取当前代理实例
return point.proceed(point.getArgs());
}
}
2、
package com.yootk.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy(exposeProxy = true) // 启用AOP配置环境
@ComponentScan("com.yootk.service") // 定义扫描包
public class AOPConfig {
}
AOPContext
- 在 AOP 进行真实类对象代理时,可以通过 exposeProxy 的配置属性来决定是否将当前代理类实例对外暴露,这样所代理的对象就会被保存在 AOPContext 中的 ThreadLocal 对象实例之中,这样开发者就可以在代理类中通过 AOPContext 直接获取真实类的对象实例
AspectJAutoProxyRegistrar
1、
package org.springframework.context.annotation;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* 在spring-aop.xsd配置文件里面也提供有同样的类定义信息
* Registers an {@link org.springframework.aop.aspectj.annotation
.AnnotationAwareAspectJAutoProxyCreator
* AnnotationAwareAspectJAutoProxyCreator} against the current {@link BeanDefinitionRegistry}
* as appropriate based on a given @{@link EnableAspectJAutoProxy} annotation.
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @see EnableAspectJAutoProxy
*/
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 向容器之中注册一个对象实例,在进行Spring源码解读的时候分析过BeanDefinitionRegistry类
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
// 解析AOP配置类(我们所定义的AOPConfig类)
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata,
EnableAspectJAutoProxy.class); // AOP注解配置类的解析
if (enableAspectJAutoProxy != null) { // 判断是否存在有注解
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { // 属性处理
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { // 属性处理
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
2、
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
// 向当前的Spring容器进行Bean的注册处理
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { // 是否存在指定Bean
// 进行Bean注册操作(获取BeanDefinition配置信息,而后再通过这个配置信息实现Bean实例化)
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) { // 类型名称,优先级控制
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
}
// cls类型为AnnotationAwareAspectJAutoProxyCreator(代理创建器)
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); // Bean定义
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); // 最高优先级
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 将当前获取到的AnnotationAwareAspectJAutoProxyCreator实例注册到Spring容器之中
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
"org.springframework.aop.config.internalAutoProxyCreator";
AnnotationAwareAspectJAutoProxyCreator
AOP 的核心逻辑在于代理对象的创建,而所有代理对象的创建是由 AnnotationAwareAspectJAutoProxyCreator 类负责处理的,同时该类也是 AOP 实现的核心结构
1、
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
// 此时所获取到的KEY的信息实际上就是Bean定义的名称
Object cacheKey = getCacheKey(beanClass, beanName); // 获取指定缓存的KEY
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) { // 是否包含有指定的KEY数据
return null; // 没有,直接返回null
}
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE); // 缓存非代理的Bean
return null; // 返回空的代理对象
}
}
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
TargetSource targetSource = getCustomTargetSource(beanClass, beanName); // 获取代理目标类型
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(
beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
2、
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
// 此时所获取到的KEY的信息实际上就是Bean定义的名称
Object cacheKey = getCacheKey(beanClass, beanName); // 获取指定缓存的KEY
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) { // 是否包含有指定的KEY数据
return null; // 没有,直接返回null
}
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE); // 缓存非代理的Bean
return null; // 返回空的代理对象
}
}
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
TargetSource targetSource = getCustomTargetSource(beanClass, beanName); // 获取代理目标类型
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(
beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName); // 获取指定Bean名称的KEY
if (this.earlyProxyReferences.remove(cacheKey) != bean) { // 判断是否存在有 Bean
return wrapIfNecessary(bean, beanName, cacheKey); // 是否要包装(代理)
}
}
return bean;
}
3、
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 在AOP最早期的实现过程里面,并不是像现在这样自定义方法的,而是会提供有一系列的Advice处理接口
// 需要在特定的类上实现特定的接口后,并且严格覆写方法,才可以实现最终的切面处理
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(
bean.getClass(), beanName, null); // 早期的Advice实现
if (specificInterceptors != DO_NOT_PROXY) { // 需要代理控制
this.advisedBeans.put(cacheKey, Boolean.TRUE); // Advice记录
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());// 代理缓存
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
createProxy()创建代理对象
1、
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) { // BeanFactory类型
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)
this.beanFactory, beanName, beanClass); // 暴露代理实例
}
ProxyFactory proxyFactory = new ProxyFactory();// 代理工厂(创建对象实例)
proxyFactory.copyFrom(this);
if (proxyFactory.isProxyTargetClass()) { // 判断代理类型
// Explicit handling of JDK proxy targets (for introduction advice scenarios)
if (Proxy.isProxyClass(beanClass)) {
// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
for (Class<?> ifc : beanClass.getInterfaces()) {
proxyFactory.addInterface(ifc);
}
}
} else {
// No proxyTargetClass flag enforced, let's apply our default checks...
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
} else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); // 早期的AOP实现
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();// 动态代理是需要类加载器的
if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
}
return proxyFactory.getProxy(classLoader); // 创建代理对象
}
2、
public Object getProxy(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
3、
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
4、
package org.springframework.aop.framework;
public interface AopProxyFactory {
AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}
5、
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() ||
hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) ||
AopProxyUtils.isLambda(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
}
6、
package org.springframework.aop.framework;
import org.springframework.lang.Nullable;
public interface AopProxy {
Object getProxy();// 使用默认类加载器获取代理实例
Object getProxy(@Nullable ClassLoader classLoader); // 使用指定类加载器获取代理类实例
}
7、
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this); // 回家了
}
8、
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
}
try {
Class<?> rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
Class<?> proxySuperClass = rootClass;
if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}
// Validate the class, writing log messages as necessary.
validateClassIfNecessary(proxySuperClass, classLoader);
// Configure CGLIB Enhancer...
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
// fixedInterceptorMap only populated at this point, after getCallbacks call above
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
// Generate the proxy class and create a proxy instance.
return createProxyClassAndInstance(enhancer, callbacks);
} catch (CodeGenerationException | IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
": Common causes of this problem include using a final class or a non-visible class",
ex);
} catch (Throwable ex) {
// TargetSource.getTarget() failed
throw new AopConfigException("Unexpected AOP exception", ex);
}
}
9、
demo