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

Spring源码解析(31)之事务配置文件解析以及核心对象创建过程

一、前言

        首先我们先准备一下spring 事务的配置文件,具体内容如下:

<?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" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"><context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driverClassName}"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" ><constructor-arg name="dataSource" ref="dataSource"></constructor-arg></bean><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean><bean id="bookService" class="com.mashibing.tx.xml.service.BookService"><property name="bookDao" ref="bookDao"></property></bean><bean id="bookDao" class="com.mashibing.tx.xml.dao.BookDao"><property name="jdbcTemplate" ref="jdbcTemplate"></property></bean><aop:config><aop:pointcut id="txPoint" expression="execution(* com.mashibing.tx.xml.*.*.*(..))"/><aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"></aop:advisor></aop:config><tx:advice id="myAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="checkout" propagation="REQUIRED" /><tx:method name="updateStock" propagation="REQUIRES_NEW" /></tx:attributes></tx:advice>
</beans>

        然后是我们对应的启动类,代码如下:

    public static void main(String[] args) throws SQLException {System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"d:\\code");ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml");BookService bookService = context.getBean("bookService", BookService.class);bookService.checkout("zhangsan",1);}

二、源码分析 

        在启动spring容器之前首先会去解析我们的配置文件,在之前介绍过解析配置文件是通过一系列重载的loadBeanDefinitionsf方法,最后就会通过调用对应parser对象解析配置文件。核心代码如下:

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) {parseDefaultElement(ele, delegate);}else {delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}}@Nullablepublic BeanDefinition parseCustomElement(Element ele) {return parseCustomElement(ele, null);}@Nullablepublic BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 获取对应的命名空间String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据命名空间找到对应的NamespaceHandlerspringNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 调用自定义的NamespaceHandler进行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}

        然后就会按照我们配置文件的解析顺序,首先就会解析aop-poincut标签

        我们看下他是如何去解析aop-pointcut标签的。

/*** 解析切入点** Parses the supplied {@code <pointcut>} and registers the resulting* Pointcut with the BeanDefinitionRegistry.*/private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {// 切入点的唯一标识String id = pointcutElement.getAttribute(ID);// 获取切入点的表达式String expression = pointcutElement.getAttribute(EXPRESSION);AbstractBeanDefinition pointcutDefinition = null;try {// 采用栈保存切入点this.parseState.push(new PointcutEntry(id));// 创建切入点bean对象// beanClass为AspectJExpressionPointcut.class。并且设置属性expression到该beanClass中pointcutDefinition = createPointcutDefinition(expression);pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));String pointcutBeanName = id;if (StringUtils.hasText(pointcutBeanName)) {// 注册bean对象parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);}else {pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);}parserContext.registerComponent(new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));}finally {// 创建后移除this.parseState.pop();}return pointcutDefinition;}

        然后继续往下跟createPointcutDefinition。

	/*** Creates a {@link BeanDefinition} for the {@link AspectJExpressionPointcut} class using* the supplied pointcut expression.*/protected AbstractBeanDefinition createPointcutDefinition(String expression) {RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);beanDefinition.setSynthetic(true);beanDefinition.getPropertyValues().add(EXPRESSION, expression);return beanDefinition;}

        这里就会设置一个AspectJExpressionPointCut对象,继续往下跟。

        此时就会往容器中注入了一个AspectJExpressionPointCut的BeanDefinition信息。继续解析下一个标签。

        此时就会解析到advisor对象,还记得之前aop中是如果解析advisor对象的吗?aop是advisor对象里面包着一层advice对象,而advice对象分为AspectJMethodBeforAdvice,AspectJAfterAdvice、AspectJAfterReturingAdvice、AspectJAroundAdvice、AspectJAfterThrowingAdvice。而事务这里这里有所不一样,我们继续往下跟。

/*** 解析advisor顾问类** Parses the supplied {@code <advisor>} element and registers the resulting* {@link org.springframework.aop.Advisor} and any resulting {@link org.springframework.aop.Pointcut}* with the supplied {@link BeanDefinitionRegistry}.*/private void parseAdvisor(Element advisorElement, ParserContext parserContext) {// 解析<aop:advisor>节点,最终创建的beanClass为`DefaultBeanFactoryPointcutAdvisor`// 另外advice-ref属性必须定义,其与内部属性adviceBeanName对应AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);String id = advisorElement.getAttribute(ID);try {// 注册到bean工厂this.parseState.push(new AdvisorEntry(id));String advisorBeanName = id;if (StringUtils.hasText(advisorBeanName)) {parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);}else {advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);}// 解析point-cut属性并赋值到DefaultBeanFactoryPointcutAdvisor#pointcut内部属性Object pointcut = parsePointcutProperty(advisorElement, parserContext);if (pointcut instanceof BeanDefinition) {advisorDef.getPropertyValues().add(POINTCUT, pointcut);parserContext.registerComponent(new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));}else if (pointcut instanceof String) {advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));parserContext.registerComponent(new AdvisorComponentDefinition(advisorBeanName, advisorDef));}}finally {this.parseState.pop();}}

        继续往下跟createAdvisorBeanDefinition看他是如何创建advisor对象的。

/*** Create a {@link RootBeanDefinition} for the advisor described in the supplied. Does <strong>not</strong>* parse any associated '{@code pointcut}' or '{@code pointcut-ref}' attributes.*/private AbstractBeanDefinition createAdvisorBeanDefinition(Element advisorElement, ParserContext parserContext) {RootBeanDefinition advisorDefinition = new RootBeanDefinition(DefaultBeanFactoryPointcutAdvisor.class);advisorDefinition.setSource(parserContext.extractSource(advisorElement));String adviceRef = advisorElement.getAttribute(ADVICE_REF);if (!StringUtils.hasText(adviceRef)) {parserContext.getReaderContext().error("'advice-ref' attribute contains empty value.", advisorElement, this.parseState.snapshot());}else {advisorDefinition.getPropertyValues().add(ADVICE_BEAN_NAME, new RuntimeBeanNameReference(adviceRef));}if (advisorElement.hasAttribute(ORDER_PROPERTY)) {advisorDefinition.getPropertyValues().add(ORDER_PROPERTY, advisorElement.getAttribute(ORDER_PROPERTY));}return advisorDefinition;}

        这里看到他注入的是DefaultBeanFactoryPointcutAdvisor对象,然后就会设置一系列属性,包括advice-ref等。

        到此,我们的aop:pointcut以及aop:advisor标签已经解析完毕,总结一下,他就是生成了AspectJExpressionPointcut以及DefaultBeanFactoryPointcutAdvisor两个BeanDefination。

        接下来就是解析对应的tx标签。

        就会在AbstractBeanDefinitionParser解析对应的tx标签。为啥会在在这里去解析,可以去看一下之前xml配置文件解析去查看是如何去查找对应的parser对象的。

         我们继续往下看具体的解析逻辑。

@Override@Nullablepublic final BeanDefinition parse(Element element, ParserContext parserContext) {AbstractBeanDefinition definition = parseInternal(element, parserContext);if (definition != null && !parserContext.isNested()) {try {String id = resolveId(element, definition, parserContext);if (!StringUtils.hasText(id)) {parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element)+ "' when used as a top-level tag", element);}String[] aliases = null;if (shouldParseNameAsAliases()) {String name = element.getAttribute(NAME_ATTRIBUTE);if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));}}// 将AbstractBeanDefinition转换为BeanDefinitionHolder并注册BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);registerBeanDefinition(holder, parserContext.getRegistry());if (shouldFireEvents()) {// 通知监听器进行处理BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);postProcessComponentDefinition(componentDefinition);parserContext.registerComponent(componentDefinition);}}catch (BeanDefinitionStoreException ex) {String msg = ex.getMessage();parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);return null;}}return definition;}

         往下跟parseInternal看他是如何生成BeanDefinition信息的。

@Overrideprotected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();String parentName = getParentName(element);if (parentName != null) {builder.getRawBeanDefinition().setParentName(parentName);}// 获取自定义标签中的class,此时会调用自定义解析器Class<?> beanClass = getBeanClass(element);if (beanClass != null) {builder.getRawBeanDefinition().setBeanClass(beanClass);}else {// 若子类没有重写getBeanClass方法则尝试检查子类是否重写getBeanClassName方法String beanClassName = getBeanClassName(element);if (beanClassName != null) {builder.getRawBeanDefinition().setBeanClassName(beanClassName);}}builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));BeanDefinition containingBd = parserContext.getContainingBeanDefinition();if (containingBd != null) {// Inner bean definition must receive same scope as containing bean.// 若存在父类则使用父类的scope属性builder.setScope(containingBd.getScope());}if (parserContext.isDefaultLazyInit()) {// Default-lazy-init applies to custom bean definitions as well.// 配置延迟加载builder.setLazyInit(true);}// 调用子类重写的doParse方法进行解析doParse(element, parserContext, builder);return builder.getBeanDefinition();}

        然后去看下对应的getClass方法。

        可以看到 他给我们返回的是一个TransactionInterceptor对象,我们来看下这个是个啥东西?

        还记得我们之前在讲aop的时候,我们要实现一个拦截器,要么就是就是实现对应的MethodInterceptor接口,要么是通过适配器来返回对应的Interceptor。而AOP也是这样子,MethodBefore跟AfterReturing是没有实现MethodInterceptor而是通过适配器来获取的。

        对应的源码位置为:DynamicAdvisedInterceptor通过对应的advisor获取对应的Interceptor的时候去获取的。

	// 将 Advisor转换为 MethodInterceptor@Overridepublic MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {List<MethodInterceptor> interceptors = new ArrayList<>(3);// 从Advisor中获取 AdviceAdvice advice = advisor.getAdvice();if (advice instanceof MethodInterceptor) {interceptors.add((MethodInterceptor) advice);}for (AdvisorAdapter adapter : this.adapters) {if (adapter.supportsAdvice(advice)) {// 转换为对应的 MethodInterceptor类型// AfterReturningAdviceInterceptor MethodBeforeAdviceInterceptor  ThrowsAdviceInterceptorinterceptors.add(adapter.getInterceptor(advisor));}}if (interceptors.isEmpty()) {throw new UnknownAdviceTypeException(advisor.getAdvice());}return interceptors.toArray(new MethodInterceptor[0]);}

        而我们知道AOP是基于IOC的一个拓展,而事务又是基于AOP的一个拓展,所以这个TransactionInterceptor他直接实现了对应的MethodIntercetor接口,他就能够实现拦截器功能,然后在对应的invoke方法实现对应的控制事务的方法,这个后续会看到,我们先继续往下看他对应的核心对象生成。

        然后我们继续接着往下看对应doParse方法。

	@Overrideprotected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element));List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);if (txAttributes.size() > 1) {parserContext.getReaderContext().error("Element <attributes> is allowed at most once inside element <advice>", element);}else if (txAttributes.size() == 1) {// Using attributes source.Element attributeSourceElement = txAttributes.get(0);RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition);}else {// Assume annotations source.builder.addPropertyValue("transactionAttributeSource",new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"));}}

        这里就会接着去解析对应的<tx:attributes>标签。

private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =new ManagedMap<>(methods.size());transactionAttributeMap.setSource(parserContext.extractSource(attrEle));for (Element methodEle : methods) {String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);TypedStringValue nameHolder = new TypedStringValue(name);nameHolder.setSource(parserContext.extractSource(methodEle));RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);if (StringUtils.hasText(propagation)) {attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);}if (StringUtils.hasText(isolation)) {attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation);}if (StringUtils.hasText(timeout)) {try {attribute.setTimeout(Integer.parseInt(timeout));}catch (NumberFormatException ex) {parserContext.getReaderContext().error("Timeout must be an integer value: [" + timeout + "]", methodEle);}}if (StringUtils.hasText(readOnly)) {attribute.setReadOnly(Boolean.parseBoolean(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));}List<RollbackRuleAttribute> rollbackRules = new ArrayList<>(1);if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);addRollbackRuleAttributesTo(rollbackRules, rollbackForValue);}if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);addNoRollbackRuleAttributesTo(rollbackRules, noRollbackForValue);}attribute.setRollbackRules(rollbackRules);transactionAttributeMap.put(nameHolder, attribute);}RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class);attributeSourceDefinition.setSource(parserContext.extractSource(attrEle));attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);return attributeSourceDefinition;}

         可以看到这里就是解析对应的method标签,并且设置对应的隔离级别,传播属性,只读,回滚等属性,最后会把这些method放在一个NameMatchTransactionAttributeSource对象中。

        看到这里可以看到,目前就往容器放入了这些BeanDefinition。

         有了这些BeanDefinition之后,接下来就是创建对应bean对象。

         接下来就会根据对应的顺序来创建对应的bean。

        此时我们来看下对应的一级缓存中已经存在了哪些对象。 

        之前我们知道第一个创建bean对象的时候,会在AspectJAwareAutoProxyCreator的before方法之前去创建对应的advisor对象,我们直接断点到那里。

         然后就会在shouldSkip中去获取的advisor然后并且创建这个advisor。

         这里就获取得到我们直接解析配置文件放入的DefaultBeanFactoryPointcutAdvisor对象。

        然后就会去创建这个advisor。

        在创建这个advisor的时候,就会属性注入的时候就创建他对应的需要的两个属性,一个是pointcut对象一个是advice对象。

        第一个循环就会去创建对应的advice对象,然后第二个就回去创建对应pointcut对象。我们这里跳过。此时我们去看一下一级缓存里面已经创建好的对象有哪些。

        此时创建advisor需要提前创建以下对象。

        首先就会去解析第一个属性advice。

        可以看到,我们的adviceName还没有去创建,只是返回了一个引用名称。

 

        然后就会去解析第二个属性,pointcut对象。

        txpoint就会去解析并且创建出来对应的对象。

 

        以下是解析代码,我们继续往下跟。

@Nullableprivate Object resolveReference(Object argName, RuntimeBeanReference ref) {try {//定义用于一个存储bean对象的变量Object bean;//获取另一个Bean引用的Bean类型Class<?> beanType = ref.getBeanType();//如果引用来自父工厂if (ref.isToParent()) {//获取父工厂BeanFactory parent = this.beanFactory.getParentBeanFactory();//如果没有父工厂if (parent == null) {//抛出Bean创建异常:无法解析对bean的引用 ref 在父工厂中:没有可以的父工厂throw new BeanCreationException(this.beanDefinition.getResourceDescription(), this.beanName,"Cannot resolve reference to bean " + ref +" in parent factory: no parent factory available");}//如果引用的Bean类型不为nullif (beanType != null) {//从父工厂中获取引用的Bean类型对应的Bean对象bean = parent.getBean(beanType);}else {//否则,使用引用的Bean名,从父工厂中获取对应的Bean对像bean = parent.getBean(String.valueOf(doEvaluate(ref.getBeanName())));}}else {//定义一个用于存储解析出来的Bean名的变量String resolvedName;//如果beanType不为nullif (beanType != null) {//解析与beanType唯一匹配的bean实例,包括其bean名NamedBeanHolder<?> namedBean = this.beanFactory.resolveNamedBean(beanType);//让bean引用nameBean所封装的Bean对象bean = namedBean.getBeanInstance();//让resolvedName引用nameBean所封装的Bean名resolvedName = namedBean.getBeanName();}else {//让resolvedName引用ref所包装的Bean名resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));//获取resolvedName的Bean对象bean = this.beanFactory.getBean(resolvedName);}//注册beanName与dependentBeanNamed的依赖关系到Bean工厂this.beanFactory.registerDependentBean(resolvedName, this.beanName);}//如果Bean对象是NullBean实例if (bean instanceof NullBean) {//将bean置为nullbean = null;}//返回解析出来对应ref所封装的Bean元信息(即Bean名,Bean类型)的Bean对象return bean;}//捕捉Bean包和子包中引发的所有异常catch (BeansException ex) {throw new BeanCreationException(this.beanDefinition.getResourceDescription(), this.beanName,"Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);}}

        此时我们的一级缓存里面只有这些对象,然后继续调用对呀的getBean。

        以上可以看到到解析adviceName的时候就只是返回了一个refName并没有去创建,而advisor则创建了出来,那advice是什么时候创建呢?

        我们再来重温一下,他是如何生成代理对象的。

	/*** 此处是真正创建aop代理的地方,在实例化之后,初始化之后就行处理* 首先查看是否在earlyProxyReferences里存在,如果有就说明处理过了,不存在就考虑是否要包装,也就是代理** Create a proxy with the configured interceptors if the bean is* identified as one to proxy by the subclass.* @see #getAdvicesAndAdvisorsForBean*/@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;}

        然后知道合适的advisor对象,我们继续往里面看他是如何去找的。

	/*** 检查前面切面解析是否有通知器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();}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集合对象之后,会在extendAdvisors中添加一个exposeInvocationInterceptor对象,我们继续往里面看。

	/*** Add an {@link ExposeInvocationInterceptor} to the beginning of the advice chain.* <p>This additional advice is needed when using AspectJ pointcut expressions* and when using AspectJ-style advice.*/@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对象,我们继续跟一下 isAspectJAdvice方法。

	/*** Determine whether the given Advisor contains an AspectJ advice.* @param advisor the Advisor to check*/private static boolean isAspectJAdvice(Advisor advisor) {return (advisor instanceof InstantiationModelAwarePointcutAdvisor ||advisor.getAdvice() instanceof AbstractAspectJAdvice ||(advisor instanceof PointcutAdvisor &&((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut));}

        然后就会去获取advice对象,继续往下跟。

	@Overridepublic Advice getAdvice() {Advice advice = this.advice;// 非Spring环境一般手动set进来,所以就直接返回吧if (advice != null) {return advice;}Assert.state(this.adviceBeanName != null, "'adviceBeanName' must be specified");Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");// 若bean是单例的,那就没什么好说的,直接去工厂里拿出来就完事了(Advice.class),有可能返回nullif (this.beanFactory.isSingleton(this.adviceBeanName)) {// Rely on singleton semantics provided by the factory.advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class);this.advice = advice;return advice;}// 若是多例的,就加锁,然后调用getBean()给他生成一个新的实例即可else {// No singleton guarantees from the factory -> let's lock locally but// reuse the factory's singleton lock, just in case a lazy dependency// of our advice bean happens to trigger the singleton lock implicitly...synchronized (this.adviceMonitor) {advice = this.advice;if (advice == null) {advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class);this.advice = advice;}return advice;}}

        可以看得到这里就会调用对应的getBean方法,把我们刚刚没创建的advice对象创建出来。 

相关文章:

Spring源码解析(31)之事务配置文件解析以及核心对象创建过程

一、前言 首先我们先准备一下spring 事务的配置文件&#xff0c;具体内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/20…...

win11安装docker报错记录

报错一&#xff1a; Docker Desktop - Unexpected WSL error An unexpected error occurred while executing a WSL command. Either shut down WSL down with wsl --shutdown, and/or reboot your machine. You can also try reinstalling WSL and/or Docker Desktop. If t…...

【vulnhub】CLAMP 1.0.1靶机

信息收集 靶机发现 端口扫描 页面访问&#xff0c;并查看源码 访问 /nt4stopc/,下面有一些问题&#xff0c;提示必须收集答案 一些判断题&#xff0c;对与错对应1与0&#xff0c;最后结果为0110111001&#xff0c;拼接访问 点击图中位置&#xff0c;发现存在参数&#xff0c;p…...

GPS跟踪环路MATLAB之——数字锁相环

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 GPS跟踪环路MATLAB之——数字锁相环 前言为什么要锁相环科斯塔斯环锁相环的一些基本概念1、捕获、锁定与跟踪的概念2、捕获时间和稳态相差3、相位捕获和频率捕获4、捕获带和同…...

docker开发环境搭建-关于数据库的IP是什么

故事的背景是这样的&#xff1a; 我在本地的ubuntu系统上安装了docker&#xff0c;并创建了一个mysql容器&#xff0c;但是在使用DBeaver连接该数据库时&#xff0c;需要填写数据库的ip&#xff0c;填写127.0.0.1&#xff0c;工具提示找不到这个库&#xff0c;然后使用ip addr…...

loginApi

import request from "/utils/request"; import { AxiosPromise } from "axios"; import { CaptchaResult, LoginData, LoginResult } from "./types";/*** 登录API** param data {LoginData}* returns*/ export function loginApi(data: LoginD…...

【RAG检索增强生成】Ollama+AnythingLLM本地搭建RAG大模型私有知识库

目录 前言一、Ollama&#xff1a;革新性的本地LLM服务工具1.核心优势2.技术亮点 二、AnythingLLM 概览1.核心特性2.技术生态支持 三、搭建本地智能知识库1. Ollama的安装启航2. AnythingLLM的安装对接3. AnythingLLM的配置精调4. 工作区与文档管理5. 聊天与检索的智能交互 四、…...

【wiki知识库】08.添加用户登录功能--前端Vue部分修改

&#x1f34a; 编程有易不绕弯&#xff0c;成长之路不孤单&#xff01; 目录 &#x1f34a; 编程有易不绕弯&#xff0c;成长之路不孤单&#xff01; 一、今日目标 二、前端Vue模块的修改 2.1 the-header组件 2.2 store工具 2.3 router路由配置修改 一、今日目标 上篇文章…...

写给非机器学习人员的 embedding 入门

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…...

Oracle【plsql编写九九乘法表】

九九乘法表 DECLAREi NUMBER : 1;j NUMBER : 1; BEGINFOR i IN 1 .. 9LOOPFOR j IN 1 .. iLOOPDBMS_OUTPUT.put (i || * || j || || i * j || );END LOOP;DBMS_OUTPUT.put_line ( );END LOOP; END;输出结果...

ansible安装K8s

部署Kubernetes (k8s) 集群使用Ansible是一个常见的自动化解决方案。下面我将提供一个基本的步骤概述和所需的命令&#xff0c;用于在CentOS 7.8上使用Ansible部署k8s集群&#xff0c;包括Master节点和Worker节点&#xff08;Web和DB节点&#xff09;。 步骤 1: 准备环境 确保所…...

restful传值

GetMapping 普通的get请求 后端&#xff1a; restfule的get请求 通过/asd/123这种方式get请求传入后端 前端 url: /system/role/deptTree/ roleId / tenantId, method: get后端PathVariable从path上取对应的值 通过 GetMapping(value "/deptTree/{roleId}/{tenan…...

Qt自定义TreeWidget,实现展开折叠按钮在右侧,且一条竖直线上对齐

效果如下&#xff1a; 图片随便找的&#xff0c;可能需要调下样式&#xff0c;代码复制可用&#xff0c;留给有需要的人。 #ifndef CustomTreeWidget_h__ #define CustomTreeWidget_h__#include <QTreeWidget> #include <QPushButton>class CCustomTreeWidget : p…...

硅步千里:如何入行?——之入行成为软件开发者

无论何时&#xff0c;你是否有遇到这样的场景&#xff08;在自己从未涉足过的行业或领域&#xff0c;现在需要自己去这个行业或领域学习探索&#xff0c;最初的目标是熟悉行业&#xff0c;快速融入进去&#xff0c;很多时候&#xff0c;我们只是了解了个大概&#xff0c;并没能…...

Sandbox: rsync.samba(80134) deny(1) file-write-create

Xcode15运行报错:Sandbox: rsync.samba(80134) deny(1) file-write-create/xxx/xxx 如下图: 解决办法: Build Settings 搜索 sandbox&#xff0c;把 Build Options 中的 User Script Sandboxing改为 NO...

lvs的dr模式综合实践

目录 ​编辑虚拟机准备工作 ​编辑​编辑​编辑 配置过程 配置client主机 配置router主机 配置lvs主机&#xff08;vip使用环回来创建&#xff09; 配置server1主机&#xff08;vip使用环回来创建&#xff09; 配置server2主机&#xff08;vip使用环回来创建&#xff0…...

什么是自然语言处理

自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;是人工智能&#xff08;AI&#xff09;的一个子领域&#xff0c;涉及计算机与人类语言的交互。它的目标是让计算机能够理解、分析、生成和操作自然语言&#xff0c;从而实现与人类的有效沟通。 自然语言处…...

快速理解互联网中的常用名词

并发与并行 并发&#xff1a;任务交替执行&#xff0c;伪并行&#xff0c;涉及CPU时间片和上下文切换。并行&#xff1a;任务真正同时执行&#xff0c;需要多核处理器&#xff0c;无上下文切换。 并发量&#xff08;Concurrency&#xff09; 概念&#xff1a;服务端程序单位…...

统计接口调用耗时_黑白名单配置

黑名单配置 黑名单就是那些被禁止访问的URL创建自定义过滤器 BlackListUrlFilter&#xff0c;并配置黑名单地址列表blacklistUrl如果有其他需求&#xff0c;还可以实现自定义规则的过滤器来满足特定的过滤要求 /*** 黑名单过滤器** author canghe*/ Component public class B…...

树莓派4 AV没有视频输出

使用AV接口输出&#xff0c;没有画面 需要在config.txt文件中 增加配置 enable_tvout1config.txt 中的 dtoverlayvc4-kms-v3d 行末尾添加,composite&#xff1a; dtoverlayvc4-kms-v3d,composite默认情况下&#xff0c;输出 NTSC 复合视频。要选择不同的模式&#xff0c;请在…...

短信群发平台:解决短信验证码接收问题的5大策略

在享受数字化服务时&#xff0c;如APP注册或网站登录&#xff0c;若遇到短信验证码无法接收的困扰&#xff0c;无疑会增添不少烦恼。为了帮助您迅速解决这一问题&#xff0c;我们精心总结了以下十大原因及对应的解决方法&#xff0c;助您顺畅完成验证流程。 一、优化网络环境 …...

WebSocket 初体验:构建实时通信应用

WebSocket是一种在客户端和服务器之间建立持久连接的协议&#xff0c;它允许双方进行双向通信&#xff0c;从而实现低延迟的数据交换。WebSocket非常适合需要实时交互的应用场景&#xff0c;比如聊天应用、在线游戏、实时数据分析等。 WebSocket简介 什么是WebSocket&#xf…...

LISA: Reasoning Segmentation via Large Language Model

发表时间&#xff1a;CVPR 2024 论文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2024/papers/Lai_LISA_Reasoning_Segmentation_via_Large_Language_Model_CVPR_2024_paper.pdf 作者单位&#xff1a;CUHK Motivation&#xff1a;尽管感知系统近年来取得了显…...

企业发展与数字化转型:创新驱动未来增长的关键策略

引言 在当今全球化和信息化高度融合的时代&#xff0c;数字化转型已经成为企业寻求增长和保持竞争优势的关键战略。随着技术的飞速进步&#xff0c;数字化不仅改变了商业模式和市场格局&#xff0c;还深刻影响了企业的内部运作和外部生态系统。大数据、人工智能、物联网等新兴技…...

如何选择适合自己的编程语言,为什么R是非计算机专业数据分析的最佳选择,五大点告诉你

在如今的数据驱动世界中&#xff0c;编程语言已成为希望在行业中进行数据分析的专业人士不可或缺的技能。对于非计算机专业背景的学者和学生来说&#xff0c;选择适合自己的编程语言可能看似困难。本文将探讨为什么对于那些需要进行本科生论文、研究生论文、或者发表学术成果的…...

【经验分享】数据结构——求树的叶子结点个数计算方法

目录 一道题就可以学会 这种题做法固定&#xff0c;记住两个公式即可 解惑&#xff1a; 1、为什么n2010110x&#xff1f; 2、为什么是n-120*410*31*210*1x*0&#xff1f; &#x1f308; 嗨&#xff0c;我是命运之光&#xff01; &#x1f30c; 2024&#xff0c;每日百字&…...

第十一章:图论part04 110.字符串接龙 105.有向图的完全可达性 106.岛屿的周长(补)

任务日期&#xff1a;7.29 题目一链接&#xff1a;110. 字符串接龙 (kamacoder.com) 思路&#xff1a;将本题寻找附近的字符串等效于寻找四周的陆地&#xff0c;即寻找周围与当前字符只有一位不同的字符串&#xff0c;然后加入到队列中并标记上&#xff0c;在此基础上要将字符…...

Linux中安装MYSQL数据库

文章目录 一、MYSQL数据库介绍1.1、MySQL数据库的基本概述1.2、MySQL数据库的主要特性1.3、MySQL数据库的技术架构与组件1.4、MySQL数据库的应用与扩展性1.5、MySQL数据库的许可模式与开源生态 二、MySQL Workbench和phpMyAdmin介绍2.1、MySQL Workbench介绍2.2、phpMyAdmin介绍…...

Vue前端服务加密后端服务解密--AES算法实现

在实际项目中考虑到用户数据的安全性&#xff0c;在用户登录时&#xff0c;前端需要对用户密码加密&#xff08;防止用户密码泄露&#xff09;&#xff0c;服务端收到登录请求时先对密码进行解密&#xff0c;然后再进行用户验证登操作。本文使用 AES ECB 模式算法来实现前端机密…...

matlab实现文字识别

在MATLAB中实现文字识别通常涉及图像处理技术和机器学习算法&#xff0c;特别是使用MATLAB内置的Image Processing Toolbox和Machine Learning Toolbox。下面是一个基本的步骤指南&#xff0c;展示如何在MATLAB中设置和执行一个简单的OCR&#xff08;Optical Character Recogni…...