IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring-源码-实例化-AOP原理 -> 正文阅读

[Java知识库]Spring-源码-实例化-AOP原理

学习本文你可以掌握
1、被切面拦截的目标类是如何实例化
2、都知道AOP是基于JDK动态代理或者Cglib,那么原理是怎么实现的

一、使用案例

1、先定义一个service方法

public interface UserService {
    void addUser();
}

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("新增用户...");
    }
}

2、在定义一个切面

@Aspect
@Component
public class SimpleAspect {

    //定义切入点表达式,拦截前面上面定义的UserService
    @Pointcut("execution(* com.xiaour.spring.boot.service.UserService.*(..))")
    private void pointcut() {
    }

    //环绕通知
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws InterruptedException {
        String methodName = proceedingJoinPoint.getSignature().getName();
        System.out.println("执行" + methodName + "的环绕通知(@Around)...");
        
        Object result = proceedingJoinPoint.proceed();//执行目标方法
    }

    // 前置通知
    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行" + methodName + "的前置通知(@Before)...");
    }

    // 后置通知
    @After("pointcut()")
    public void after(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行" + methodName + "的后置通知(@After)...");
    }

    // 返回通知
    @AfterReturning("pointcut()")
    public void afterReturning(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行" + methodName + "的后置返回通知(@AfterReturning)...");
    }
}

3、开启Aspect注解

@EnableAspectJAutoProxy //开启AOP注解
@SpringBootApplication
public class Application {
	......
}

至此Service的任意方法被调用都会进到切面的通知方法里面去。

二、下面开始介绍下原理

1、先从@EnableAspectJAutoProxy注解开始说

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
	boolean proxyTargetClass() default false;
	boolean exposeProxy() default false;
}

该注解2个属性
proxyTargetClass :AOP代理的具体实现方式。英文注释很简单哈,是否开启CGLIB(基于子类)的代理,为true的话使用CGLIB,为false的话使用基于标准 Java 接口的代理,也就是JDK动态代理。如果是true,目标类是接口还是会使用动态代理。
exposeProxy:控制代理的暴露方式。是否暴露当前代理对象为ThreadLocal模式。可以解决内部调用不能使用切面的问题,为true的话可以获取代理对象,通过代理对象调用方法进入切面。

//实例伪代码
T proxy=(T) AopContext.currentProxy();

@Import(AspectJAutoProxyRegistrar.class)先导入AspectJAutoProxyRegistrar类,看下该类会做什么事情


public class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
			AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
		}
		if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
			AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
		}
	}
}
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
	return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
	return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

最终向容器中添加一个类AnnotationAwareAspectJAutoProxyCreator,该类一层层实现很多接口,顶层有个BeanPostProcessor,所以AnnotationAwareAspectJAutoProxyCreator也算一个BeanPostProcessor,也会被添加到BeanPostProcessor执行链中去,然后在Bean初始化前后调用BeanPostProcessor的前置后置回调方法,AnnotationAwareAspectJAutoProxyCreator在调用后置回调方法时,会返回一个代理代理对象,从而生成了bean的代理实例。

2、下面就开始创建userService普通实例,然后执行设置属性方法populateBean和初始化方法initializeBean

3、执行前置-初始化-后置方法

4、执行AnnotationAwareAspectJAutoProxyCreator的后置方法,生成userService代理对象,替换传进来的userService的普通实例对象

5、执行AnnotationAwareAspectJAutoProxyCreator父类的postProcessAfterInitialization,里面会执行最为重要的方法-wrapIfNecessary

6、下面介绍下wrapIfNecessary方法

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	.....
	//判断cacheKey是否属于通知的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;
	}

	// Create proxy if we have advice.//获取通知拦截器
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	if (specificInterceptors != DO_NOT_PROXY) {
		this.advisedBeans.put(cacheKey, Boolean.TRUE);
		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;
}

先获取通知拦截器,判断userService是否是符合通知拦截器定义的规则,也就是是否符合切点表达式定义的规则,如果符合则返回一系列通知方法,下面截了一张specificInterceptors的内容,其实就是我们上面定义切面里面的四个通知

protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
	....

	ProxyFactory proxyFactory = new ProxyFactory();
	proxyFactory.copyFrom(this);

	//设置ProxyTargetClass的值,这个和前面提到的注解EnableAspectJAutoProxy的ProxyTargetClass属性是一个东西
	if (!proxyFactory.isProxyTargetClass()) {
		if (shouldProxyTargetClass(beanClass, beanName)) {
			proxyFactory.setProxyTargetClass(true);
		}
		else {
			evaluateProxyInterfaces(beanClass, proxyFactory);
		}
	}

	//拿到所有通知,也就是切面里面定义切面里面的四个通知
	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
	for (Advisor advisor : advisors) {
		proxyFactory.addAdvisor(advisor);
	}

	proxyFactory.setTargetSource(targetSource);//设置被代理对象
	customizeProxyFactory(proxyFactory);

	proxyFactory.setFrozen(this.freezeProxy);
	if (advisorsPreFiltered()) {
		proxyFactory.setPreFiltered(true);
	}

	return proxyFactory.getProxy(getProxyClassLoader());
}

最终调用proxyFactory.getProxy,下面看ProxyFactory的getProxy方法是做了啥

public Object getProxy(ClassLoader classLoader) {
	return createAopProxy().getProxy(classLoader);
}

调用createAopProxy方法里面会先拿到aopProxyFactory,然后调用aopProxyFactory的createAopProxy

protected final synchronized AopProxy createAopProxy() {
	if (!this.active) {
		activate();
	}
	return getAopProxyFactory().createAopProxy(this);
}

private AopProxyFactory aopProxyFactory;
public AopProxyFactory getAopProxyFactory() {
	return this.aopProxyFactory;
}

下面看aopProxyFactory

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
	if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
		Class<?> targetClass = config.getTargetClass();
		if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
			return new JdkDynamicAopProxy(config);
		}
		return new ObjenesisCglibAopProxy(config);
	}
	else {
		return new JdkDynamicAopProxy(config);
	}
}

更具条件是选择JDK代理还是Cglib代理,最后将生成的代理类一步一步返回

假设上面返回JdkDynamicAopProxy对象,当调用JdkDynamicAopProxy的getProxy方法是如何创建代理对象的

注意接口参数里面的interfaces的值,是一个数组,不是单个接口,而是多个接口

InvocationHandler其实就是JdkDynamicAopProxy对象,里面实现了invoke方法,下面我们看下当我们调用UserService里面的方法时,invoke里面做了什么?

拿到调用链,也就是由目标方法+切面环绕通知+前置通知+后置通知组成的调用链一层一层执行下去

至此所有流程执行完成了。

总结:Spring会先加载AspectJAutoProxyRegistrar类,AspectJAutoProxyRegistrar会像容器注册AnnotationAwareAspectJAutoProxyCreator,AnnotationAwareAspectJAutoProxyCreator是一个BeanPostProcess类型的Bean,在实例化其他普通Bean的时候,会执行该bean的前置和后置方法,在AnnotationAwareAspectJAutoProxyCreator后置方法里面会创建代理对象,当我们执行目标方法时,会进入JdkDynamicAopProxy的invoke方法,然后拿到调用链,挨个调用。

三、疑问

1、在执行wrapIfNecessary方法的时候,getAdvicesAndAdvisorsForBean方法会返回当前Bean的所有通知方法,也就是下图的specificInterceptors,那么这个数组是怎么得到的

解答:

1、在解答前先说下在启动的时候,我们手动添加的@EnableAspectJAutoProxy注解,里面会注册一个AnnotationAwareAspectJAutoProxyCreator,这是一个BeanPostProcess

注册时设置的值,主要包含设置下面2个属性

  1. order = Integer.MIN_VALUE
  2. beanName = org.springframework.transaction.config.internalTransactionAdvisor

注册AnnotationAwareAspectJAutoProxyCreator必定会实例化它,看下AnnotationAwareAspectJAutoProxyCreator的初始化方法

public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {
	//这个2个属性非常重要,下面会介绍aspectJAdvisorsBuilder
	private AspectJAdvisorFactory aspectJAdvisorFactory;
	private BeanFactoryAspectJAdvisorsBuilder aspectJAdvisorsBuilder;
	//aspectJAdvisorsBuilder主要完成判断Bean是否被切入点表达式拦截的对象

	//初始化方法的时候会为上面2个属性设置值
	@Override
	protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		super.initBeanFactory(beanFactory);
		if (this.aspectJAdvisorFactory == null) {
			this.aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory);
		}
		this.aspectJAdvisorsBuilder = new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory);
	}	

}

BeanFactoryAspectJAdvisorsBuilder类,里面有几个Map,存的是带有@Aspect的通知方法集合,就比如类SimpleAspect带有@Aspect注解,那么SimpleAspect就是一个切面,切面里面一定有切点+通知方法,那么这些通知方法都存在aspectJAdvisorsBuilder的Map里面,至于怎么知道SimpleAspect类带有@Aspect注解,以及找出他的通知方法,都是由aspectJAdvisorsBuilder来实现的。
那么什么时候触发去解析的SimpleAspect里面的通知呢?在实例Bean实例化的时候,下面开始讲解

先看创建Bean时会调用一个resolveBeforeInstantiation方法,也就是下图说的

resolveBeforeInstantiation是调用doCreateBean之前调用的,也叫实例化前置方法。

下面看下resolveBeforeInstantiation怎么做的

在一对BeanPostProcess集合中,轮训调用实例化前置方法,当然也包含AnnotationAwareAspectJAutoProxyCreator

1、AnnotationAwareAspectJAutoProxyCreator是InstantiationAwareBeanPostProcessor的子类
2、实现了postProcessBeforeInstantiation方法

下面看下AnnotationAwareAspectJAutoProxyCreator的postProcessBeforeInstantiation方法,shouldSkip方法很重要

@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
	List<Advisor> candidateAdvisors = findCandidateAdvisors();
	for (Advisor advisor : candidateAdvisors) {
		if (advisor instanceof AspectJPointcutAdvisor) {
			if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
				return true;
			}
		}
	}
	return super.shouldSkip(beanClass, beanName);
}

shouldSkip会调用findCandidateAdvisors方法,下面看下findCandidateAdvisors做了什么?

最终是通过前面说到的aspectJAdvisorsBuilder对象,调用buildAspectJAdvisors方法完成解析找到带有@Aspect注解的类,并且解析里面的通知方法。

上图主要流程,拿到所有BeanName判断这个Bean是否带有@Aspect注解,如果有那么解析这个Bean的所有通知方法。看下是如何判断是否带有@Aspect注解的

AnnotationUtils.findAnnotation(clazz, Aspect.class) != null

下面说下处理@Aspect类的逻辑

List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);

主要是把里面的方法转成Advisor对象,看下最终包含哪些方法

1、SimpleAspect.around
2、SimpleAspect.before
3、SimpleAspect.after
4、SimpleAspect.afterReturning

注意是不包含切入点表达式的方法,只有通知,再回到上面wrapIfNecessary

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-26 11:28:38  更:2022-04-26 11:29:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:10:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码