Doc site
https://docs.spring.io/spring-framework/docs/5.3.24/reference/html/core.html#aop-api
名词解释
Joinpoint:连接点
Pointcut:切入点
Advice:增强 (方位点 + 增强逻辑)
Target:被代理对象
Proxy:指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象
Weaving:织入。 它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊
Aspect:切面。面是由多个点组成的。那么:
Aspect切⾯= 切⼊点+增强 = 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑
区分:
Joinpoint 指的是候选者,啥意思呢?
就是说,每一个被增强的方法,提供了几个候选的时机,包括方法执行前、方法执行后、方法执行出现异常、 方法执行前和后的综合
注意,Joinpoint 指的是候选者,那么一旦选定了某个具体的时机,这个具体的时机,就叫做 方位点。也就是说,方位点是JoinPoint的子集,或者说具体值
advice 和 aspect的概念区分,如下图:
切入点表达式
参考: https://www.cnblogs.com/zhangxufeng/p/9160869.html
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下:
- modifiers-pattern:方法的可见性,如public,protected;
- ret-type-pattern:方法的返回值类型,如int,void等;
- declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
- name-pattern:方法名类型,如buisinessService();
- param-pattern:方法的参数类型,如java.lang.String;
- throws-pattern:方法抛出的异常类型,如java.lang.Exception;
其中,访问修饰符可以省略
在切入点表达式中,有2种通配符:
*
..
其中,*可以用于除了param-pattern的所有位置
而 .. 只可以用于declaring-type-pattern 和 param-pattern
- *通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。
如下示例表示返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法:
execution(* com.spring.service.BusinessObject.*())
下述示例表示返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法:
execution(* com.spring.service.Business*.*())
- ..通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。
如下示例表示匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数:
execution(* com.spring.service..*.businessService())
这里需要说明的是,包路径service...businessService()中的..应该理解为延续前面的service路径,表示到service路径为止,或者继续延续service路径,从而包括其子包路径;后面的.businessService(),这里的*表示匹配一个单词,因为是在方法名前,因而表示匹配任意的类。
如下示例是使用..表示任意个数的参数的示例,需要注意,表示参数的时候可以在括号中事先指定某些类型的参数,而其余的参数则由..进行匹配:
execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))
参数列表
基本数据类型直接写名称,如:int
* *..*.*(int)
1
引用类型写包名
java.lang.String
demo
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wp</groupId>
<artifactId>aop-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>aop-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/greeting")
public String greeting(@RequestParam("name") String name){
return name + ",nice to meet you";
}
}
package com.wp.aopdemo.aspect;
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;
/**
* 类描述:
*
* @Author weipeng
* @Date 2021/10/27
* @Version 1.0
*/
@Aspect
@Component
public class WeipengAspect {
@Pointcut("(execution(* com.wp.aopdemo.controller..*.*(..)))")
public void baseWebLog(){
}
@Around("baseWebLog()")
public Object aroundWp(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("before controller invoke-----------------");
Object result = joinPoint.proceed();
System.out.println("after controller invoke |||||||||||||||||,result is:"+result);
return result;
}
}
测试效果如下:
before controller invoke-----------------
after controller invoke |||||||||||||||||,result is:zhangsan,nice to meet you
进阶
如果,我们有多个pointcut,那么应该怎么写呢?
package com.wp.aopdemo.aspect;
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;
/**
* 类描述:
*
* @Author weipeng
* @Date 2021/10/27
* @Version 1.0
*/
@Aspect
@Component
public class WeipengAspect {
@Pointcut("(execution(* com.wp.aopdemo.controller..*.*(..)))")
public void baseWebLog(){
}
@Pointcut("(execution(* com.wp.aopdemo.optimize.controller..*.*(..)))")
public void optimizeWebLog(){
}
// 这个将2个pointcut,组合成1个pointcut
@Pointcut("baseWebLog() || optimizeWebLog()")
public void compositeWebLog(){
}
@Around("compositeWebLog()")
public Object aroundWp(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("before controller invoke-----------------");
Object result = joinPoint.proceed();
System.out.println("after controller invoke |||||||||||||||||,result is:"+result);
return result;
}
}
/**
* 类描述:
*
* @Author weipeng
* @Date 2021/10/27
* @Version 1.0
*/
@RestController
@RequestMapping("/optimize")
public class OptimizeController {
@RequestMapping("/test")
public String testOptimize(@RequestParam("param") String optimizeParam) {
System.out.println("OptimizeController testOptimize---------");
return optimizeParam;
}
}
测试
http://localhost:8083/hello/greeting?name=zhangsan
http://localhost:8083/optimize/test?param=gogogo
效果如下:
before controller invoke-----------------
after controller invoke |||||||||||||||||,result is:zhangsan,nice to meet you
before controller invoke-----------------
OptimizeController testOptimize---------
after controller invoke |||||||||||||||||,result is:gogogo
pointcut使用@annotation来表示
The next two examples show how to match the execution of methods annotated with an @Auditable
annotation and extract the audit code:
The first of the two examples shows the definition of the @Auditable
annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
The second of the two examples shows the advice that matches the execution of @Auditable
methods:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
别人写的logAspect
package com.iot.framework.aspectj;
import java.util.Collection;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import com.alibaba.fastjson2.JSON;
import com.iot.common.annotation.Log;
import com.iot.common.core.domain.entity.SysUser;
import com.iot.common.core.domain.model.LoginUser;
import com.iot.common.enums.BusinessStatus;
import com.iot.common.enums.HttpMethod;
import com.iot.common.filter.PropertyPreExcludeFilter;
import com.iot.common.utils.SecurityUtils;
import com.iot.common.utils.ServletUtils;
import com.iot.common.utils.StringUtils;
import com.iot.common.utils.ip.IpUtils;
import com.iot.framework.manager.AsyncManager;
import com.iot.framework.manager.factory.AsyncFactory;
import com.iot.system.domain.SysOperLog;
/**
* 操作日志记录处理
*
* @author yanggq
*/
@Aspect
@Component
public class LogAspect
{
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
/** 排除敏感属性字段 */
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
{
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
{
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
{
try
{
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
// 获取用户对应租户的数据,操作日志也可能需要租户隔离
SysUser loginSysUser = SecurityUtils.getLoginUser().getUser();
operLog.setTenantId(loginSysUser.getTenantId());
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip);
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (loginUser != null)
{
operLog.setOperName(loginUser.getUsername());
}
if (e != null)
{
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
catch (Exception exp)
{
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
{
// 设置action动作
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
operLog.setTitle(log.title());
// 设置操作人类别
operLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request,参数和值
if (log.isSaveRequestData())
{
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog);
}
// 是否需要保存response,参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
{
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
}
/**
* 获取请求的参数,放到log中
*
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
{
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
{
String params = argsArrayToString(joinPoint.getArgs());
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
}
else
{
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray)
{
if (StringUtils.isNotNull(o) && !isFilterObject(o))
{
try
{
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter());
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
}
}
}
}
return params.trim();
}
/**
* 忽略敏感属性
*/
public PropertyPreExcludeFilter excludePropertyPreFilter()
{
return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES);
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o)
{
Class<?> clazz = o.getClass();
if (clazz.isArray())
{
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
}
else if (Collection.class.isAssignableFrom(clazz))
{
Collection collection = (Collection) o;
for (Object value : collection)
{
return value instanceof MultipartFile;
}
}
else if (Map.class.isAssignableFrom(clazz))
{
Map map = (Map) o;
for (Object value : map.entrySet())
{
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
源码
在学习aop源码之前,我们需要有个大概思路
怎么实现代理:增强逻辑代码 + 在原来bean实例化和初始化的流程,无痕的嵌入增强逻辑代码(通过beanPostProcessor实现)
首先,明确的是,aop是通过beanPostProcessor来实现的,确切的说,是通过AbstractAutoProxyCreator实现的,具体实现类是AnnotationAwareAspectJAutoProxyCreator
AbstractAutoProxyCreator(postProcessAfterInitialization)。
在aop代理中,对指定方法进行增强,就是通过这个AbstractAutoProxyCreator实现的。在AbstractAutowireCapableBeanFactory 中的doCreateBean方法中,对象实例化完成后,populateBean方法之后,会调用initializeBean方法,在initializeBean方法中,会调用applyBeanPostProcessorsAfterInitialization,在applyBeanPostProcessorsAfterInitialization中,会调用AbstractAutoProxyCreator的postProcessAfterInitialization的方法,执行postProcessAfterInitialization方法,会返回一个代理后的对象
以jdk动态代理举例:
1.我们的逻辑是,最终将增强逻辑代码 塞到invocaitonHandler的invoke方法中,这样当执行invoke方法的时候,就会执行增强逻辑代码。但是这并不是说,将增强逻辑代码copy一份,写到invoke方法中,而是要使用将增强逻辑代码封装到 一个接口中,将这个接口的实现类引用X 传递到invocaitonHandler中,这样invoke方法中,直接调用 引用X的某些方法 就可以执行 增强逻辑代码了。
第一步完成的结果 ,就是返回一个invocaitonHandler实现类对象,这个对象中包含 增强逻辑代码。
增强逻辑,最后会构建成一个对象,类型是Advisor,具体实现类是InstantiationModelAwarePointcutAdvisorImpl
2.另外,我们在原始对象构建完成后,想办法 对原始对象进行代理,生成代理后的对象,将代理后的对象 提供给用户,这样,用户就会使用代理后的对象 执行某些方法。那么spring是怎么实现生成代理后的对象?就是通过beanPostProcessor实现的 (在实现类中,使用Proxy.newProxyInstance生成代理后的对象) mybatis是使用interceptorChain.pluginAll来实现 生成代理后的对象的。
第二步的结果就是 ,返回一个代理后的对象。
第一步和第二步的具体实现代码 其实同时写在invocaitonHandler实现类中。
获取第一步的invocaitonHandler实现类对象的方法 是:createAopProxy(这个最好使用接口工厂,因为返回有jdk和cglib两种)
获取第二步的代理后的对象的方法 是:getProxy(这个最好使用接口,因为实现 代理原对象的代码也有2种)
jdk动态代理:1.想办法先返回一个InvocationHandler实现类对象(这个对象中包含增强逻辑代码),2.然后使用Proxy.newProxyInstance,生成一个代理后的对象。
做第一件事情,我们用到了 AopProxyFactory、AopProxy、DefaultAopProxyFactory、JdkDynamicAopProxy
做第二件事情,我们用到了ProxyCreatorSupport、ProxyFactory
因为我们需要同时使用jdk 和cglib,jdk 返回的是InvocationHandler实现类对象,cglib返回的是MethodInterceptor对象,这个时候,我们需要统一。怎么统一呢?很简单,都实现一个接口AopProxy,这样,我们就能用 AopProxy引用 指向InvocationHandler实现类对象 或者 MethodInterceptor对象
第三件事:因为需要将代理嵌入到ioc流程中,所以这里需要使用beanPostProcessor来连接。即在ioc流程中,实例化对象完成后,执行beanPostProcessor的postProcessAfterInitialization,从而生成代理对象 到ioc容器中。
所以做第三件事情,使用到了beanPostProcessor、AutoProxyCreator(beanPostProcessor的实现类)、BeanFactory
为什么需要一个ProxyFactory,还需要一个 ProxyCreatorSupport呢?
ProxyCreatorSupport是ProxyFactory的父类,ProxyFactory中写一些主线的逻辑,ProxyCreatorSupport中写具体的实现细节的逻辑。这样别人调用ProxyFactory的方法时,就会发现逻辑简单,直接找到他想要的方法,不需要看一些复杂的逻辑。 有点类似于门面模式。
而ProxyFactory可以直接调用 ProxyCreatorSupport中的方法,这样就可以实现灵活复杂的逻辑了。也就是说,ProxyFactory写大方向,ProxyCreatorSupport中写具体的细节。
另外,ProxyFactory中的方法可以使用public修饰,ProxyCreatorSupport中的方法可以使用protected修饰
获取方法参数
在增强方法中,怎么获取到被增强方法的参数呢?
增强方法:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod(String,int)") // 这个是被增强方法
public void audit(JoinPoint joinPoint) {// 这个是增强方法
joinPoint.getArgs(); // 返回anyPublicMethod方法的参数,一个是String类型的,一个是int类型的
}
Access to the Current JoinPoint
Any advice method may declare, as its first parameter, a parameter of type org.aspectj.lang.JoinPoint
(note that around advice is required to declare a first parameter of type ProceedingJoinPoint
, which is a subclass of JoinPoint
. The JoinPoint
interface provides a number of useful methods:
getArgs()
: Returns the method arguments.getThis()
: Returns the proxy object.getTarget()
: Returns the target object.getSignature()
: Returns a description of the method that is being advised.toString()
: Prints a useful description of the method being advised.
除了上面使用的JoinPoint,还可以使用args
来获取参数
Passing Parameters to Advice
We have already seen how to bind the returned value or exception value (using after returning and after throwing advice). To make argument values available to the advice body, you can use the binding form of args
. If you use a parameter name in place of a type name in an args expression, the value of the corresponding argument is passed as the parameter value when the advice is invoked. An example should make this clearer. Suppose you want to advise the execution of DAO operations that take an Account
object as the first parameter, and you need access to the account in the advice body. You could write the following:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
The args(account,..)
part of the pointcut expression serves two purposes. First, it restricts matching to only those method executions where the method takes at least one parameter, and the argument passed to that parameter is an instance of Account
. Second, it makes the actual Account
object available to the advice through the account
parameter.
Another way of writing this is to declare a pointcut that “provides” the Account
object value when it matches a join point, and then refer to the named pointcut from the advice. This would look as follows:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
还可以使用returning
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
还可以使用throwing:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
环绕通知(增强)
5中增强中,环绕增强是一种比较特殊的增强,因为环绕增强,通过ProceedingJoinPoint对象,可以控制业务逻辑是否执行
public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知中的beforemethod....");
Object result = null;
try{
// 控制原有业务逻辑是否执行
// result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
}catch(Exception e) {
System.out.println("环绕通知中的exceptionmethod....");
}finally {
System.out.println("环绕通知中的after method....");
}
return result;
}
如果上面的环绕增强中,没有proceedingJoinPoint.proceed这行代码,那么原有的业务逻辑不会执行。
AOP源码学习
在学习aop源码之前,我们需要有个大概思路
怎么实现代理:增强逻辑代码 + 在原来bean实例化和初始化的流程,无痕的嵌入增强逻辑代码(通过beanPostProcessor实现)
首先,明确的是,aop是通过beanPostProcessor来实现的,确切的说,是通过AbstractAutoProxyCreator实现的
AbstractAutoProxyCreator(postProcessAfterInitialization)。
在aop代理中,对指定方法进行增强,就是通过这个AbstractAutoProxyCreator实现的。在AbstractAutowireCapableBeanFactory 中的doCreateBean方法中,对象实例化完成后,populateBean方法之后,会调用initializeBean方法,在initializeBean方法中,会调用applyBeanPostProcessorsAfterInitialization,在applyBeanPostProcessorsAfterInitialization中,会调用AbstractAutoProxyCreator的postProcessAfterInitialization的方法,执行postProcessAfterInitialization方法,会返回一个代理后的对象
以jdk动态代理举例:
1.我们的逻辑是,最终将增强逻辑代码 塞到invocaitonHandler的invoke方法中,这样当执行invoke方法的时候,就会执行增强逻辑代码。但是这并不是说,将增强逻辑代码copy一份,写到invoke方法中,而是要使用将增强逻辑代码封装到 一个接口中,将这个接口的实现类引用X 传递到invocaitonHandler中,这样invoke方法中,直接调用 引用X的某些方法 就可以执行 增强逻辑代码了。
第一步完成的结果 ,就是返回一个invocaitonHandler实现类对象,这个对象中包含 增强逻辑代码。
2.另外,我们在原始对象构建完成后,想办法 对原始对象进行代理,生成代理后的对象,将代理后的对象 提供给用户,这样,用户就会使用代理后的对象 执行某些方法。那么spring是怎么实现生成代理后的对象?就是通过beanPostProcessor实现的 (在实现类中,使用Proxy.newProxyInstance生成代理后的对象) mybatis是使用interceptorChain.pluginAll来实现 生成代理后的对象的。
第二步的结果就是 ,返回一个代理后的对象。
第一步和第二步的具体实现代码 其实同时写在invocaitonHandler实现类中。
获取第一步的invocaitonHandler实现类对象的方法 是:createAopProxy(这个最好使用接口工厂,因为返回有jdk和cglib两种)
获取第二步的代理后的对象的方法 是:getProxy(这个最好使用接口,因为实现 代理原对象的代码也有2种)
jdk动态代理:1.想办法先返回一个InvocationHandler实现类对象(这个对象中包含增强逻辑代码),2.然后使用Proxy.newProxyInstance,生成一个代理后的对象。
做第一件事情,我们用到了 AopProxyFactory、AopProxy、DefaultAopProxyFactory、JdkDynamicAopProxy
做第二件事情,我们用到了ProxyCreatorSupport、ProxyFactory
因为我们需要同时使用jdk 和cglib,jdk 返回的是InvocationHandler实现类对象,cglib返回的是MethodInterceptor对象,这个时候,我们需要统一。怎么统一呢?很简单,都实现一个接口AopProxy,这样,我们就能用 AopProxy引用 指向InvocationHandler实现类对象 或者 MethodInterceptor对象
第三件事:因为需要将代理嵌入到ioc流程中,所以这里需要使用beanPostProcessor来连接。即在ioc流程中,实例化对象完成后,执行beanPostProcessor的postProcessAfterInitialization,从而生成代理对象 到ioc容器中。
所以做第三件事情,使用到了beanPostProcessor、AutoProxyCreator(beanPostProcessor的实现类)、BeanFactory
为什么需要一个ProxyFactory,还需要一个 ProxyCreatorSupport呢?
ProxyCreatorSupport是ProxyFactory的父类,ProxyFactory中写一些主线的逻辑,ProxyCreatorSupport中写具体的实现细节的逻辑。这样别人调用ProxyFactory的方法时,就会发现逻辑简单,直接找到他想要的方法,不需要看一些复杂的逻辑。 有点类似于门面模式。
而ProxyFactory可以直接调用 ProxyCreatorSupport中的方法,这样就可以实现灵活复杂的逻辑了。也就是说,ProxyFactory写大方向,ProxyCreatorSupport中写具体的细节。
另外,ProxyFactory中的方法可以使用public修饰,ProxyCreatorSupport中的方法可以使用protected修饰
起名字
应该将 区别放在前面,相同的放在后面
比如,现在有个接口Connection,那么他的实现类 应该是 zhangConnection、hefeiConnection
如果是子类与父类的关系:那么父类应该是带有support, 子类是真正工作的类,子类调用父类中方法工作
比如 父类是ProxyCreatorSupport 子类是ProxyCreator
工厂模式
工厂是干什么的?其实,很简单,现在有好几个东西 可供选择,正常情况下,我们会使用if...else...进行条件判断,进而根据不同的条件 选择不同的东西。
但是 这种写法不太优雅,所以 我们可以将f...else...的代码 移动到工厂类中,对外是统一的入口方法、统一的返回类型。
另外,if...else...中的代码可能比较复杂,这种情况下,我们可以使用父子类,即入口工厂是个子类,还有一个工厂支撑类是父类,入口工厂写大方向代码,工厂支撑类中写细节代码。入口工厂调用工厂支撑类的方法 完成功能
如何为 Pointcut 匹配的类生成动态代理类
refer to : https://blog.csdn.net/wang489687009/article/details/120919094
前言
通过前面文章的分析,我们知道,实现 Spring AOP 大体会分如下几步:
找到与 bean 匹配的所有的 Advisor
使用所有匹配的 Advisor 来为 bean 生成生成动态代理
通过动态代理类执行 Advice
将 Spring AOP 与 Spring IoC 进行结合
上一篇我们分析了 Pointcut 是通过 AspectJExpressionPointcut 来处理 aspectj expression 的匹配的。
本文接着来分析一下,Spring AOP 是如何通过匹配的 Advisor 来给 bean 生成代理类的。
版本约定
Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)
正文
为了研究 Spring 是如何通过匹配的 Advisor 来给 bean 生成代理类的,我们需要先找到 proxy bean 是什么时候创建的,我们可以从 bean 的创建三步曲中着手,我们在 AbstractAutowireCapableBeanFactory#initializeBean() 方法处打上断点来观察 bean 的创建情况。
initializeBean() 会去调用 applyBeanPostProcessorsAfterInitialization(),断点可以打在这个方法上:
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
如何快速找到 Spring 在哪里产生的代理 bean?
答:我们在 applyBeanPostProcessorsAfterInitialization() 方法处打一个条件断点,断点的条件为:result != current,这样的话,就能快速定位到是哪个 processor 生成了代理类!
什么时候创建 proxy bean
可以发现,proxy bean 是在执行 AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization() 时产生的:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
// AbstractAutoProxyCreator#wrapIfNecessary()
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
......
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理 bean
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;
}
如何创建 proxy bean
通过阅读上面的源码,我们可以得出:
Spring 在创建每个 proxy bean 时,都会 new 一个 ProxyFactory 来进行处理
Spring 在创建代理类之前会将 targetClass 暴露到 BeanDefinition 的 attribute 中
proxyTargetClass 可以由用户指定,但不是完全受用户控制的。如果 proxyTargetClass=false 的话,Spring 会通过内部的 check 来校正 proxyTargetClass 的值
ProxyFactory 使用 cglib 还是 jdk proxy?
// DefaultAopProxyFactory#createAopProxy()
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)) {
return new JdkDynamicAopProxy(config);
}
// proxyTargetClass=true && targetClass 不是接口时,使用 cglib
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
spring 使用的 cglib 代理 还是 jdk proxy?
使用 cglib 还是 jdk 代理,大体是由两个因素共同决定的:一是 targetClass 的类型(接口 or 实体类);二是 proxyTargetClass 标识的值(true or false)。
而 proxyTargetClass 的值是由用户和 spring 框架共同决定的。
引用官方的话说:
Spring 默认使用的是 JDK proxy,如果 target object 实现了接口的话。
Spring 会使用 CGLIB proxy,如果 target object 没有实现接口的话。
怎么去读取 @Pointcut、@Around、@Before、@After、@AfterReturning、@AfterThrowing 等 Advice 标识
支持的注解:
protected enum AspectJAnnotationType {
AtPointcut, AtAround, AtBefore, AtAfter, AtAfterReturning, AtAfterThrowing
}
protected static class AspectJAnnotation<A extends Annotation> {
private static Map<Class<?>, AspectJAnnotationType> annotationTypeMap = new HashMap<>(8);
static {
annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut);
annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround);
annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore);
annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter);
annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning);
annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing);
}
......
}
小结
Spring 是在 bean 创建时的第三步 initializeBean 时,调用 AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization() 来产生代理 bean 的。
每个代理 bean 的生成都是通过一个新 new 出来的 ProxyFactory 来处理的。