当前位置: 首页 > news >正文

Spring源码解析(27)之AOP的核心对象创建过程2

一、前言

        我们在上一节中已经介绍了Advisor的创建过程,当时我们创建的logUtil这bean,他在

resolveBeforeInstantiation返回的是null,那么就会继续往下执行doCreateBean方法。

二、源码分析

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.// 这个beanWrapper是用来持有创建出来的bean对象的BeanWrapper instanceWrapper = null;// 获取factoryBean实例缓存if (mbd.isSingleton()) {// 如果是单例对象,从factorybean实例缓存中移除当前bean定义信息instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}// 没有就创建实例if (instanceWrapper == null) {// 根据执行bean使用对应的策略创建新的实例,如,工厂方法,构造函数主动注入、简单初始化instanceWrapper = createBeanInstance(beanName, mbd, args);}// 从包装类中获取原始beanObject bean = instanceWrapper.getWrappedInstance();// 获取具体的bean对象的Class属性Class<?> beanType = instanceWrapper.getWrappedClass();// 如果不等于NullBean类型,那么修改目标类型if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}// Allow post-processors to modify the merged bean definition.// 允许beanPostProcessor去修改合并的beanDefinitionsynchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {try {// MergedBeanDefinitionPostProcessor后置处理器修改合并bean的定义applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Post-processing of merged bean definition failed", ex);}mbd.postProcessed = true;}}// Eagerly cache singletons to be able to resolve circular references// even when triggered by lifecycle interfaces like BeanFactoryAware.// 判断当前bean是否需要提前曝光:单例&允许循环依赖&当前bean正在创建中,检测循环依赖boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}// 为避免后期循环依赖,可以在bean初始化完成前将创建实例的ObjectFactory加入工厂addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));//			//只保留二级缓存,不向三级缓存中存放对象
//			earlySingletonObjects.put(beanName,bean);
//			registeredSingletons.add(beanName);
//
//			synchronized (this.singletonObjects) {
//				if (!this.singletonObjects.containsKey(beanName)) {
//					//实例化后的对象先添加到三级缓存中,三级缓存对应beanName的是一个lambda表达式(能够触发创建代理对象的机制)
//					this.singletonFactories.put(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
//					this.registeredSingletons.add(beanName);
//				}
//			}}// Initialize the bean instance.// 初始化bean实例Object exposedObject = bean;try {// 对bean的属性进行填充,将各个属性值注入,其中,可能存在依赖于其他bean的属性,则会递归初始化依赖的beanpopulateBean(beanName, mbd, instanceWrapper);// 执行初始化逻辑exposedObject = initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {throw (BeanCreationException) ex;}else {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);}}if (earlySingletonExposure) {// 从缓存中获取具体的对象Object earlySingletonReference = getSingleton(beanName, false);// earlySingletonReference只有在检测到有循环依赖的情况下才会不为空if (earlySingletonReference != null) {// 如果exposedObject没有在初始化方法中被改变,也就是没有被增强if (exposedObject == bean) {exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {// 返回false说明依赖还没实例化好if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}// 因为bean创建后所依赖的bean一定是已经创建的// actualDependentBeans不为空则表示当前bean创建后其依赖的bean却没有全部创建完,也就是说存在循环依赖if (!actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,"Bean with name '" + beanName + "' has been injected into other beans [" +StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +"] in its raw version as part of a circular reference, but has eventually been " +"wrapped. This means that said other beans do not use the final version of the " +"bean. This is often the result of over-eager type matching - consider using " +"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");}}}}// Register bean as disposable.try {// 注册bean对象,方便后续在容器销毁的时候销毁对象registerDisposableBeanIfNecessary(beanName, bean, mbd);}catch (BeanDefinitionValidationException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);}return exposedObject;}

        然后就会进入到 initializeBean,进行bean的初始化,我们之前都知道在这个initializeBean会会执行BPP的after方法,然后我们之前解析AOP配置文件的时候注入的AutoProxyCreator他是一个BeanPostProcessor,这里会执行它的after方法来判断是否需要创建代理对象,我们继续往下跟。

/*** 初始化给定的bean实例,应用工厂回调以及init方法和BeanPostProcessors** Initialize the given bean instance, applying factory callbacks* as well as init methods and bean post processors.* <p>Called from {@link #createBean} for traditionally defined beans,* and from {@link #initializeBean} for existing bean instances.* @param beanName the bean name in the factory (for debugging purposes)* @param bean the new bean instance we may need to initialize* @param mbd the bean definition that the bean was created with* (can also be {@code null}, if given an existing bean instance)* @return the initialized bean instance (potentially wrapped)* @see BeanNameAware* @see BeanClassLoaderAware* @see BeanFactoryAware* @see #applyBeanPostProcessorsBeforeInitialization* @see #invokeInitMethods* @see #applyBeanPostProcessorsAfterInitialization*/protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {// 如果安全管理器不为空if (System.getSecurityManager() != null) {// 以特权的方式执行回调bean中的Aware接口方法AccessController.doPrivileged((PrivilegedAction<Object>) () -> {invokeAwareMethods(beanName, bean);return null;}, getAccessControlContext());}else {// Aware接口处理器,调用BeanNameAware、BeanClassLoaderAware、beanFactoryAwareinvokeAwareMethods(beanName, bean);}Object wrappedBean = bean;//如果mdb不为null || mbd不是"synthetic"。一般是指只有AOP相关的prointCut配置或者Advice配置才会将 synthetic设置为trueif (mbd == null || !mbd.isSynthetic()) {// 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessBeforeInitialization初始化方法。// 返回的Bean实例可能是原始Bean包装器wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {//调用初始化方法,先调用bean的InitializingBean接口方法,后调用bean的自定义初始化方法invokeInitMethods(beanName, wrappedBean, mbd);}catch (Throwable ex) {//捕捉调用初始化方法时抛出的异常,重新抛出Bean创建异常:调用初始化方法失败throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null),beanName, "Invocation of init method failed", ex);}//如果mbd为null || mbd不是"synthetic"if (mbd == null || !mbd.isSynthetic()) {// 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessAfterInitialization方法。// 返回的Bean实例可能是原始Bean包装器wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}//返回包装后的Beanreturn wrappedBean;}

 

        我们继续往里面跟AutoProxyCretor的after方法,看里面具体的执行逻辑。

	@Overridepublic Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {//初始化结果对象为result,默认引用existingBeanObject result = existingBean;//遍历该工厂创建的bean的BeanPostProcessors列表for (BeanPostProcessor processor : getBeanPostProcessors()) {//回调BeanPostProcessor#postProcessAfterInitialization来对现有的bean实例进行包装Object current = processor.postProcessAfterInitialization(result, beanName);//一般processor对不感兴趣的bean会回调直接返回result,使其能继续回调后续的BeanPostProcessor;// 但有些processor会返回null来中断其后续的BeanPostProcessor// 如果current为nullif (current == null) {//直接返回result,中断其后续的BeanPostProcessor处理return result;}//让result引用processor的返回结果,使其经过所有BeanPostProcess对象的后置处理的层层包装result = current;}//返回经过所有BeanPostProcess对象的后置处理的层层包装后的resultreturn result;}

  

        AbstractAutoProxyCreator的postProcessAfterInitialization方法。他里面会调用具体的wrapIfNecessary来判断是否要创建代理对象。
	@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {// 获取当前bean的key:如果beanName不为空,则以beanName为key,如果为FactoryBean类型,// 前面还会添加&符号,如果beanName为空,则以当前bean对应的class为keyObject cacheKey = getCacheKey(bean.getClass(), beanName);// 判断当前bean是否正在被代理,如果正在被代理则不进行封装if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 如果它需要被代理,则需要封装指定的beanreturn wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}

        这里的前面判断逻辑是跟之前是一样,首先会在缓存里面判断当前bean是否已经被处理过,很明显我们当前的logUtil之前已经被解析过并且放入到了缓存中,因为我们的logUtil他是一个切面逻辑他不需要被创建代理对象。

        

        其次会判断当前的bean是否是基础bean,如果是则不用创建带来对象,如果不是则会去盗用shouldSkip方法来判断当前的bean是否需要被跳过,而在这这个shouldSkip方法中我们知道,会寻找容器中的advisor对象。

/*** 先判断是否已经处理过,是否需要跳过,跳过的话直接就放进advisedBeans里,表示不进行代理,如果这个bean处理过了,获取通知拦截器,然后开始进行代理** Wrap the given bean if necessary, i.e. if it is eligible for being proxied.* @param bean the raw bean instance* @param beanName the name of the bean* @param cacheKey the cache key for metadata access* @return a proxy wrapping the bean, or the raw bean instance as-is*/protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 如果已经处理过,直接返回if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}// 这里advisedBeans缓存了已经进行了代理的bean,如果缓存中存在,则可以直接返回if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 这里isInfrastructureClass()用于判断当前bean是否为Spring系统自带的bean,自带的bean是// 不用进行代理的;shouldSkip()则用于判断当前bean是否应该被略过if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {// 对当前bean进行缓存this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.// 获取当前bean的Advices和AdvisorsObject[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);// 对当前bean的代理状态进行缓存if (specificInterceptors != DO_NOT_PROXY) {// 对当前bean的代理状态进行缓存this.advisedBeans.put(cacheKey, Boolean.TRUE);// 根据获取到的Advices和Advisors为当前bean生成代理对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 缓存生成的代理bean的类型,并且返回生成的代理beanthis.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}

        然后我们跳过logUtil,看一下真正需要被创建代理对象的 myCalculator。

        他会进入到 getAdvicesAndAdvisorsForBean方法中,这个方法就是给当前的bean寻找适配的advisor对象,我们知道advisor对象中包含advice对象,advice对象中包含MethodLocadingFactory还有AspectJExpressionPointcut和SimpleBeanFactoryAwareAspectInstanceFactory对象。

        思考一个问题,我们根据什么条件给bean对象创建代理对象,肯定是根据我们配置express表达式来判断哪些包那些类那些对象需要创建代理对象,而Spring的匹配规则主要是通过ClassFilter和MethodMatcher对象来实现的,之前我们只知道advice对象需要一个AspectJExpressionPointCut对象,我们来看下这个类的继承关系图。

@FunctionalInterface
public interface ClassFilter {/*** 是否应该将pointcut应用到给定的接口或者类上** Should the pointcut apply to the given interface or target class?* @param clazz the candidate target class* @return whether the advice should apply to the given target class*/boolean matches(Class<?> clazz);/*** ClassFilter的规范实例能够匹配所有的类** Canonical instance of a ClassFilter that matches all classes.*/ClassFilter TRUE = TrueClassFilter.INSTANCE;}
/*** 一个特殊类型的MethodMatcher接口,当匹配方法的时候需要将说明考虑进去** A specialized type of {@link MethodMatcher} that takes into account introductions* when matching methods. If there are no introductions on the target class,* a method matcher may be able to optimize matching more effectively for example.** @author Adrian Colyer* @since 2.0*/
public interface IntroductionAwareMethodMatcher extends MethodMatcher {/*** Perform static checking whether the given method matches. This may be invoked* instead of the 2-arg {@link #matches(java.lang.reflect.Method, Class)} method* if the caller supports the extended IntroductionAwareMethodMatcher interface.* @param method the candidate method* @param targetClass the target class* @param hasIntroductions {@code true} if the object on whose behalf we are* asking is the subject on one or more introductions; {@code false} otherwise* @return whether or not this method matches statically*/boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions);}

        有了以上知识,我们继续往下跟getAdvicesAndAdvisorsForBean方法。

	/*** 检查前面切面解析是否有通知器advisors创建,有就返回,没有就是null* @param beanClass the class of the bean to advise* @param beanName the name of the bean* @param targetSource* @return*/@Override@Nullableprotected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {// 找合适的增强器对象List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);// 若为空表示没找到if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();}

        里面是通过findEligibleAdvisors查找合适的advisor对象。

	/*** 找到所有符合条件的通知对于自动代理的类** Find all eligible Advisors for auto-proxying this class.* @param beanClass the clazz to find advisors for* @param beanName the name of the currently proxied bean* @return the empty List, not {@code null},* if there are no pointcuts or interceptors* @see #findCandidateAdvisors* @see #sortAdvisors* @see #extendAdvisors*/protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 将当前系统中所有的切面类的切面逻辑进行封装,从而得到目标AdvisorList<Advisor> candidateAdvisors = findCandidateAdvisors();// 对获取到的所有Advisor进行判断,看其切面定义是否可以应用到当前bean,从而得到最终需要应用的AdvisorList<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);// 提供的hook方法,用于对目标Advisor进行扩展extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {// 对需要代理的Advisor按照一定的规则进行排序eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;}

        findCandidateAdvisors我们在之前就比较熟悉了,他是寻找容器中所有的advisor对象,他在之前第一次调用的时候就会初始化所有的advisor对象,并且把他放入到缓存中去。

        获取得到所有的advisor对象之后就会通过findAdvisorsThatCanApply查找可以应用在当前类的advisor对象,如果找不到适配的advisor对象说明当前对象不需要被代理,直接返回空,表示不需要被代理,我们继续往下看他的匹配过程。   

	/*** 检测实例化之后的bean是否需要通知器,其实就是检测方法或者类上是否需要事务注解** Search the given candidate Advisors to find all Advisors that* can apply to the specified bean.* @param candidateAdvisors the candidate Advisors* @param beanClass the target's bean class* @param beanName the target's bean name* @return the List of applicable Advisors* @see ProxyCreationContext#getCurrentProxiedBeanName()*/protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {// 从候选的通知器中找到合适正在创建的实例对象的通知器return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);}finally {ProxyCreationContext.setCurrentProxiedBeanName(null);}}

        继续往下跟findAdvisorsThatCanApply方法。

	/*** 遍历每一个advisor,然后判断是否可以应用到目标类clazz上,可以的话就加入候选列表** Determine the sublist of the {@code candidateAdvisors} list* that is applicable to the given class.* @param candidateAdvisors the Advisors to evaluate* @param clazz the target class* @return sublist of Advisors that can apply to an object of the given class* (may be the incoming List as-is)*/public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {// 若候选的增强器集合为空 直接返回if (candidateAdvisors.isEmpty()) {return candidateAdvisors;}// 定义一个合适的增强器集合对象List<Advisor> eligibleAdvisors = new ArrayList<>();// 循环我们候选的增强器对象,IntroductionAdvisor与PointcutAdvisor本质区别,一个是可以作用在类上,一个是可以作用的方法上for (Advisor candidate : candidateAdvisors) {// 判断我们的增强器对象是不是实现了IntroductionAdvisor (很明显我们事务的没有实现 所以不会走下面的逻辑)if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {eligibleAdvisors.add(candidate);}}// 是否有引介增强boolean hasIntroductions = !eligibleAdvisors.isEmpty();for (Advisor candidate : candidateAdvisors) {// 判断我们的增强器对象是不是实现了IntroductionAdvisorif (candidate instanceof IntroductionAdvisor) {// already processed// 在上面已经处理过,不需要处理continue;}// 真正的判断增强器是否合适当前类型if (canApply(candidate, clazz, hasIntroductions)) {eligibleAdvisors.add(candidate);}}return eligibleAdvisors;}

        第一个循环我们所有获取得到的advisor对象,判断当前的advisor对象是否是引介增强,之前我们都知道,我们AOP创建的advisor都是AspectJPointcutAdvisor对象,所以我们跳过,直接看canApply方法。

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {// 如果是IntroductionAdvisor的话,则调用IntroductionAdvisor类型的实例进行类的过滤// 这里是直接调用的ClassFilter的matches方法if (advisor instanceof IntroductionAdvisor) {return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);}// 通常我们的Advisor都是PointcutAdvisor类型else if (advisor instanceof PointcutAdvisor) {// 转为PointcutAdvisor类型PointcutAdvisor pca = (PointcutAdvisor) advisor;//这里从Advisor中获取Pointcut的实现类 这里是AspectJExpressionPointcutreturn canApply(pca.getPointcut(), targetClass, hasIntroductions);}else {// It doesn't have a pointcut so we assume it applies.return true;}}

        这里开始也是有一个advisor对象的类型判断,我们继续往下跟他具体实现的canApply方法。

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {Assert.notNull(pc, "Pointcut must not be null");// 进行切点表达式的匹配最重要的就是ClassFilter和MethodMatcher这两个方法的实现。// MethodMatcher中有两个matches方法。一个参数是只有Method对象和targetClass,另一个参数有// Method对象和targetClass对象还有一个Method的方法参数,他们两个的区别是:// 两个参数的matches是用于静态的方法匹配 三个参数的matches是在运行期动态的进行方法匹配的// 先进行ClassFilter的matches方法校验// 首先这个类要在所匹配的规则下if (!pc.getClassFilter().matches(targetClass)) {return false;}// 再进行MethodMatcher方法级别的校验MethodMatcher methodMatcher = pc.getMethodMatcher();if (methodMatcher == MethodMatcher.TRUE) {// No need to iterate the methods if we're matching any method anyway...return true;}// 判断匹配器是不是IntroductionAwareMethodMatcherIntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;if (methodMatcher instanceof IntroductionAwareMethodMatcher) {introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;}// 创建一个集合用于保存targetClass的class对象Set<Class<?>> classes = new LinkedHashSet<>();// 判断当前class是不是代理的class对象if (!Proxy.isProxyClass(targetClass)) {// 加入到集合中去classes.add(ClassUtils.getUserClass(targetClass));}// 获取到targetClass所实现的接口的class对象,然后加入到集合中classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));// 循环所有的class对象for (Class<?> clazz : classes) {// 通过class获取到所有的方法Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);// 循环我们的方法for (Method method : methods) {// 只要有一个方法能匹配到就返回true// 这里就会有一个问题:因为在一个目标中可能会有多个方法存在,有的方法是满足这个切点的匹配规则的// 但是也可能有一些方法是不匹配切点规则的,这里检测的是只有一个Method满足切点规则就返回true了// 所以在运行时进行方法拦截的时候还会有一次运行时的方法切点规则匹配if (introductionAwareMethodMatcher != null ?introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :// 通过方法匹配器进行匹配methodMatcher.matches(method, targetClass)) {return true;}}}return false;}

        这里就比较有意思了,首先获取对应的classFilter进行类级别的匹配,然后会在循环方法进行方法级别的匹配。

 

        这里看得到匹配到一个方法就返回true,因为这里只是单纯的判断当前类是否需要创建代理对对象,在后续的创建代理对象的时候会再具体进行匹配,所以这里匹配到一个就直接返回true。

        进过以上一系列判断,我们就可以获取得到当前bean适配的所有的advisor对象。

       

         获取得到对应的所有的advisor对象的时候,对调用一个对这些advisor进行拓extendAdvisors,我们继续往下跟。

	@Overrideprotected void extendAdvisors(List<Advisor> candidateAdvisors) {AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors);}public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {// Don't add advisors to an empty list; may indicate that proxying is just not requiredif (!advisors.isEmpty()) {boolean foundAspectJAdvice = false;for (Advisor advisor : advisors) {// Be careful not to get the Advice without a guard, as this might eagerly// instantiate a non-singleton AspectJ aspect...if (isAspectJAdvice(advisor)) {foundAspectJAdvice = true;break;}}if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {advisors.add(0, ExposeInvocationInterceptor.ADVISOR);return true;}}return false;}

        可以看得到他只是给我们的advisor集合中添加了ExposeInvocationInterceptor,这个类的作用是啥呢?在解决这个问题之前,首先问一下大家,我们生成的这个advisor对象在执行切面逻辑的时候是不是会有执行顺序,答案是肯定会有的,那我们如果控制这些advisor的执行顺序呢?

        我们看下ExposeInvocationIntercetor里面有啥。

        其实他就是通过这个ExposeInvocationInterceptor里面维护的一个ThreadLocal来控制当前的执行的advisor对象 。

	public static final Advisor ADVISOR = new DefaultPointcutAdvisor(INSTANCE) {@Overridepublic String toString() {return ExposeInvocationInterceptor.class.getName() +".ADVISOR";}};private static final ThreadLocal<MethodInvocation> invocation =new NamedThreadLocal<>("Current AOP method invocation");/*** 此处是继续调用ReflectiveMethodInvocation的proceed方法来进行递归调用** Return the AOP Alliance MethodInvocation object associated with the current invocation.* @return the invocation object associated with the current invocation* @throws IllegalStateException if there is no AOP invocation in progress,* or if the ExposeInvocationInterceptor was not added to this interceptor chain*/public static MethodInvocation currentInvocation() throws IllegalStateException {MethodInvocation mi = invocation.get();if (mi == null) {throw new IllegalStateException("No MethodInvocation found: Check that an AOP invocation is in progress and that the " +"ExposeInvocationInterceptor is upfront in the interceptor chain. Specifically, note that " +"advices with order HIGHEST_PRECEDENCE will execute before ExposeInvocationInterceptor! " +"In addition, ExposeInvocationInterceptor and ExposeInvocationInterceptor.currentInvocation() " +"must be invoked from the same thread.");}return mi;}

        既然我们的advisor有执行顺序,所以spring接下来就是对获取得到的advisor对象进行排序。

	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 将当前系统中所有的切面类的切面逻辑进行封装,从而得到目标AdvisorList<Advisor> candidateAdvisors = findCandidateAdvisors();// 对获取到的所有Advisor进行判断,看其切面定义是否可以应用到当前bean,从而得到最终需要应用的AdvisorList<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);// 提供的hook方法,用于对目标Advisor进行扩展extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {// 对需要代理的Advisor按照一定的规则进行排序eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;}

        这里的排序就比较麻烦了,他使用到的是一个拓扑有限无环排序,有兴趣的伙伴可以去研究一下这个算法。

        最后就会返回排好序的advisor对象,接下来就是真正创建代理对象。在看创建代理对象的之前,如果对cglib跟jdk动态代理源码不是很熟悉的先去看一下之前的博客Spring源码解析(24)之JDK动态代理与cglib动态代理源码解析_spring 选择 jdk 代理 cglib 代理源码-CSDN博客 

        接下来会先画一个图总结一下,这三篇博客的脉络,然后再往下看他是如何创建代理对象的。 

相关文章:

Spring源码解析(27)之AOP的核心对象创建过程2

一、前言 我们在上一节中已经介绍了Advisor的创建过程&#xff0c;当时我们创建的logUtil这bean&#xff0c;他在 resolveBeforeInstantiation返回的是null&#xff0c;那么就会继续往下执行doCreateBean方法。 二、源码分析 protected Object doCreateBean(String beanName,…...

【题解】【数学】—— [CSP-J 2023] 小苹果

【题解】【数学】—— [CSP-J 2023] 小苹果 [CSP-J 2023] 小苹果题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 1.题意分析2.代码 [CSP-J 2023] 小苹果 前置知识&#xff1a;数学分组思想&#xff0c;整体思想。 [CSP-J 2023] 小苹果 题目描述 小 Y 的桌子上…...

python实现微信聊天图片DAT文件还原

完整代码如下&#xff1a; from glob import glob import os from tqdm import tqdmdef get_sign(dat_r):signatures [(0x89, 0x50, 0x4e), (0x47, 0x49, 0x46), (0xff, 0xd8, 0xff)]mats [".png", ".gif", ".jpg"]for now in dat_r:for j, x…...

栈与队列——1.有效的括号

力扣题目链接 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串&#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。注意空字符串可被认为是有效…...

C语言家教记录(二)

C语言家教记录&#xff08;二&#xff09; 导语输入输出表达式算数运算符示例程序赋值运算符简单赋值复合赋值 总结和复习 导语 本次授课内容如下&#xff1a;输入输出、表达式 有时间则讲解选择语句 辅助教材为 《C语言程序设计现代方法&#xff08;第2版&#xff09;》 输…...

Cocos Creator2D游戏开发(10)-飞机大战(8)-计分和结束

现在游戏基本能完了, 飞机能发射子弹,打了敌机,敌机也能炸; 接下来要做计分了; 步骤: 搞出一个lable让lable显示炸了多少飞机 开搞: ①创建一个Lable标签 ② root.ts文件 添加 property(Label) player_score: Label; // 标签属性 标签绑定 ③ 代码添加 注册 然后回调 contac…...

经验分享:大数据多头借贷风险对自身的不利影响?

在现代金融体系中&#xff0c;大数据技术的应用使得多头借贷成为一种普遍现象。多头借贷指的是个人或企业在短时间内同时或近期内申请多笔贷款或信用产品&#xff0c;这种行为可能带来一系列财务和信用风险。以下是大数据多头借贷风险对个人自身可能产生的不利影响&#xff1a;…...

OpenCV 图像处理 轮廓检测基本原理

文章目录 基本原理关键函数和参数注意事项 示例代码示例效果代码详解findContours 函数原型findContours函数变体 基本原理 轮廓发现是图像处理中的一个重要步骤&#xff0c;用于检测物体的边界和形状。 图像预处理&#xff1a; 轮廓发现通常在灰度图像上进行。因此&#xff0…...

C 语言动态顺序表

test.h #ifndef _TEST_H #define _TEST_H #include <stdio.h> #include <stdlib.h> #include <string.h>typedef int data_type;// 定义顺序表结构体 typedef struct List{data_type *data; // 顺序表数据int size; // 顺序表当前长度int count; // 顺序表容…...

擅于辩论的人可以将黑的说成白的,但是存在无法解决的矛盾

擅于辩论的人有能力通过逻辑、证据和修辞等手段&#xff0c;巧妙地引导听众接受与事实相反的观点。 然而&#xff0c;这并不意味着擅于辩论的人就能将任何事物都颠倒黑白。辩论的基础是事实和逻辑&#xff0c;即使是最优秀的辩手&#xff0c;也必须遵循这些基本原则。如果某个…...

java的命令执行漏洞揭秘

0x01 前言 在Java中可用于执行系统命令常见的方式有两种&#xff0c;API为&#xff1a;java.lang.Runtime、java.lang.ProcessBuilder 0x02 java.lang.Runtime GetMapping("/runtime/exec")public String CommandExec(String cmd) {Runtime run Runtime.getRunti…...

爬虫中常见的加密算法Base64伪加密,MD5加密【DES/AES/RSA/SHA/HMAC】及其代码实现(一)

目录 基础常识 Base64伪加密 python代码实现 摘要算法 1. MD5 1.1 JavaScript 实现 1.2 Python 实现 2. SHA 2.1 JavaScript 实现 2.2 Python 实现 2.3 sha系列特征 3. HMAC 3.1 JavaScript 实现 3.2 Python 实现 对称加密 一. 常见算法归纳 1. 工作模式归纳 …...

C语言数据在内存中的存储超详解

文章目录 1. 整数在内存中的存储2. 大小端字节序和字节序判断2. 1 什么是大小端&#xff1f;2. 2 为什么会有大小端&#xff1f;2. 3 练习 3. 浮点数在内存中的存储3. 1 一个代码3. 2 浮点数的存储3. 2. 1 浮点数存的过程3. 2. 2 浮点数取的过程3. 3 题目解析 1. 整数在内存中的…...

【大模型】【NL2SQL】基本原理

三个输入&#xff1a; prompt 用户输入 数据库表格等信息 sql 语句...

RK3568平台(显示篇)DRM vop驱动程序分析

一.设备树配置 vopb: vopff900000 {compatible "rockchip,rk3399-vop-big";reg <0x0 0xff900000 0x0 0x2000>, <0x0 0xff902000 0x0 0x1000>;interrupts <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>;assigned-clocks <&cru ACLK_VOP0>, &…...

vue3 动态加载组件

//模版调用 <component :is"geticon(item.icon)" />//引入 import { ref, onMounted, markRaw, defineAsyncComponent } from vue;//异步添加icon图标组建 function geticon(params) {const modules import.meta.glob(../components/icons/*.vue);const link …...

Latex on overleaf入门语法

Latex on overleaf入门语法 前言基本结构序言 简单的格式化命令添加注释&#xff1a;%加粗、斜体、下划线有序列表、无序列表 添加图片图片的标题、标签和引用 添加表格一个简单的表格为表格添加边框标题、标签、引用 数学表达式基本的数学命令 基本格式摘要段落、新行章节、分…...

使用Echarts来实现数据可视化

目录 一.什么是ECharts? 二.如何使用Springboot来从后端给Echarts返回响应的数据&#xff1f; eg:折线图&#xff1a; ①Controller层&#xff1a; ②service层&#xff1a; 一.什么是ECharts? ECharts是一款基于JavaScript的数据可视化图标库&#xff0c;提供直观&…...

一文搞懂GIT

文章目录 1. GiT概述1.1 GIT概述1.2 GIT安装 2. GIT组成3. GIT基本命令3.1 基本命令3.2 分支操作3.3 远程操作3.4 标签操作3.5 其他命令 1. GiT概述 1.1 GIT概述 Git 是一个分布式版本控制系统&#xff0c;被广泛应用于软件开发中。 Git 具有众多优点&#xff0c;比如&#…...

jQuery入门(四)案例

jQuery 操作入门案例 一、复选框案例 功能: 列表的全选&#xff0c;反选&#xff0c;全不选功能实现。 实现步骤和分析&#xff1a; - 全选 1. 为全选按钮绑定单击事件。 2. 获取所有的商品项复选框元素&#xff0c;为其添加 checked 属性&#xff0c;属性值为 true。 -…...

揭秘MITM攻击:原理、手法与防范措施

中间人攻击发生时&#xff0c;攻击者会在通讯两端之间插入自己&#xff0c;成为通信链路的一部分。攻击者可以拦截、查看、修改甚至重新定向受害者之间的通信数据&#xff0c;而不被双方察觉。这种攻击常见于未加密的Wi-Fi网络、不安全的HTTP连接或者通过社会工程学手段诱导受害…...

【YOLOv8】一文全解+亮点介绍+训练教程+独家魔改优化技巧

前言 Hello&#xff0c;大家好&#xff0c;我是cv君&#xff0c;最近开始在空闲之余&#xff0c;经常更新文章啦&#xff01;除目标检测、分类、分隔、姿态估计等任务外&#xff0c;还会涵盖图像增强领域&#xff0c;如超分辨率、画质增强、降噪、夜视增强、去雾去雨、ISP、海…...

创建mvp ubo(uniform buffer object)

创建过程&#xff1a; 创建一个uniform buffer查找buffer memory requirements分配、绑定buffer memorymap buffer memory拷贝mvp data to buffer memoryunmap buffer memory 示例代码&#xff1a; glm::mat4 projection glm::perspective(glm::radians(45.0f), 1.0f, 0.1f…...

1.GPIO

理论说明 输入 上拉输入&#xff1a;拉高电平 下拉输入&#xff1a;拉低电平 浮空输入&#xff1a;不拉高也不拉低电平 输出 开漏输出&#xff1a;不能输出高电平&#xff08;P-MOS不可用&#xff0c;则只能低电平&#xff09; 推挽输出&#xff1a;可输出高低电平 输出速率…...

C++必修:STL之vector的了解与使用

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 贝蒂的主页&#xff1a;Betty’s blog 1. C/C中的数组 1.1. C语言中的数组 在 C 语言中&#xff0c;数组是一组相同类型…...

【MySQL】索引 【上】 {没有索引的查询/磁盘/mysql与磁盘IO/初识索引}

文章目录 1.没有索引存在的问题2. 认识磁盘MySQL与存储MySQL与磁盘交互基本单位建立共识图解IO认识索引 在关系数据库中&#xff0c;索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构&#xff0c;它是某个表中一列或若干列值的集合和相应的指向表中物…...

GO goroutine状态流转

Gidle -> Grunnable newproc获取新的goroutine&#xff0c;并放置到P运行队列中 这也是go关键字之后实际编译调用的方法 func newproc(fn *funcval) {// 获取当前正在运行中的goroutinegp : getg()// 获取调用者的程序计数器地址&#xff0c;用于调试和跟踪pc : getcallerp…...

DLMS/COSEM中的信息安全:DLMS/COSEM安全概念(上)

DLMS/COSEM中的信息安全描述并规定: ——DLMS/COSEM安全概念; ——选择加密算法; ——安全密钥; ——使用加密算法进行实体认证、xDLMS APDU保护和COSEM数据保护。 1.综述 DLMS/COSEM服务器的资源(COSEM对象属性和方法)可以由在应用连接内的DLMS/COSEM客户机访问。 在AA…...

C语言第九天笔记

数组的概念 什 么是数组 数组是 相同类型&#xff0c; 有序数据的集合。 数 组的特征 数组中的数据被称为数组的 元素&#xff0c;是同构的 数组中的元素存放在内存空间里 (char player_name[6]&#xff1a;申请在内存中开辟6块连续的基于char类 型的变量空间) 衍生概念&…...

智慧环卫可视化:科技赋能城市清洁管理

图扑智慧环卫可视化通过实时监控、数据分析和智能调度&#xff0c;提高环卫作业效率&#xff0c;优化资源配置&#xff0c;提升城市清洁水平&#xff0c;实现城市管理的精细化和现代化。...

silverlight做的网站/德阳seo

Cesium解决传感器抖动问题 解决问题原理&#xff1a;在基本步长的基础上&#xff0c;为每一段轨道再进行细化&#xff08;使用拉格朗日插值算法进行计算差值&#xff09; // 传入轨道数据&#xff0c;传出positionProperty对象、开始时间、结束时间 import * as Cesium from …...

看设计作品的网站软件/滨州seo招聘

俗话说工欲善其事&#xff0c;必先利其器做Python开发同样也是这样一个道理&#xff0c;那么Python用什么开发工具呢?其实Python开发工具能用的有很多的&#xff0c;今天小编就为大家推荐几款&#xff0c;这些里面总有一款适合你。NO1. PycharmPycharm可以应用于写相应脚本和程…...

国外wordpress cms主题/人民日报评网络暴力

1.计算机病毒的结构主要包括&#xff08;&#xff09;模块。 A.传染 B.触发 C.破坏 D.复制 2.与专用会计核算软件相比较&#xff0c;通用会计核算软件的优点有&#xff08;&#xff09;。 A.成本相对较低 B.维护量小且维护有保障 C.开发者决定系统的扩充与修改 D.软件开发水…...

互联网网站开发服务合同/网上兼职外宣推广怎么做

BZOJ放这种丝帛我也是醉了... 不过来填一下求最小循环节的坑... 以这道题为例,相同文本串粘起来的串中取一小节,可以把任意一个字符看做文本串头. 那么我们一次KMP求出next函数然后显见,最后一个字符它会与上一个循环串的尾匹配,所以减一减就好啦... /* # Last modified: 2016-…...

做外贸网站平台/seo搜外

你有个任务&#xff0c;需要用到某个开源项目;或者老大交代你一个事情&#xff0c;让你去了解某个东西。怎么下手呢&#xff1f;如何开始呢&#xff1f;我的习惯是这样&#xff1a; 1. 首先&#xff0c;查找和阅读该项目的博客和资料&#xff0c;通过google你能找到某个项目大体…...

日记类型 wordpress/网络营销软件商城

Jocky是金蝶中间件技术领袖袁红岗先生的个人作品&#xff08;旧有名称JOC)。原本是方便Apusic 应用服务器的开发&#xff0c;现在开放出来&#xff0c;供大家自由使用 ... Jocky提供了对IDE Eclipse的支持&#xff0c;同时&#xff0c;也支持在Ant中使用&#xff08;事实上&…...