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 事务的配置文件,具体内容如下: <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/20…...
win11安装docker报错记录
报错一: 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靶机
信息收集 靶机发现 端口扫描 页面访问,并查看源码 访问 /nt4stopc/,下面有一些问题,提示必须收集答案 一些判断题,对与错对应1与0,最后结果为0110111001,拼接访问 点击图中位置,发现存在参数,p…...
GPS跟踪环路MATLAB之——数字锁相环
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 GPS跟踪环路MATLAB之——数字锁相环 前言为什么要锁相环科斯塔斯环锁相环的一些基本概念1、捕获、锁定与跟踪的概念2、捕获时间和稳态相差3、相位捕获和频率捕获4、捕获带和同…...
docker开发环境搭建-关于数据库的IP是什么
故事的背景是这样的: 我在本地的ubuntu系统上安装了docker,并创建了一个mysql容器,但是在使用DBeaver连接该数据库时,需要填写数据库的ip,填写127.0.0.1,工具提示找不到这个库,然后使用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:革新性的本地LLM服务工具1.核心优势2.技术亮点 二、AnythingLLM 概览1.核心特性2.技术生态支持 三、搭建本地智能知识库1. Ollama的安装启航2. AnythingLLM的安装对接3. AnythingLLM的配置精调4. 工作区与文档管理5. 聊天与检索的智能交互 四、…...
【wiki知识库】08.添加用户登录功能--前端Vue部分修改
🍊 编程有易不绕弯,成长之路不孤单! 目录 🍊 编程有易不绕弯,成长之路不孤单! 一、今日目标 二、前端Vue模块的修改 2.1 the-header组件 2.2 store工具 2.3 router路由配置修改 一、今日目标 上篇文章…...
写给非机器学习人员的 embedding 入门
你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…...
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是一个常见的自动化解决方案。下面我将提供一个基本的步骤概述和所需的命令,用于在CentOS 7.8上使用Ansible部署k8s集群,包括Master节点和Worker节点(Web和DB节点)。 步骤 1: 准备环境 确保所…...
restful传值
GetMapping 普通的get请求 后端: restfule的get请求 通过/asd/123这种方式get请求传入后端 前端 url: /system/role/deptTree/ roleId / tenantId, method: get后端PathVariable从path上取对应的值 通过 GetMapping(value "/deptTree/{roleId}/{tenan…...
Qt自定义TreeWidget,实现展开折叠按钮在右侧,且一条竖直线上对齐
效果如下: 图片随便找的,可能需要调下样式,代码复制可用,留给有需要的人。 #ifndef CustomTreeWidget_h__ #define CustomTreeWidget_h__#include <QTreeWidget> #include <QPushButton>class CCustomTreeWidget : p…...
硅步千里:如何入行?——之入行成为软件开发者
无论何时,你是否有遇到这样的场景(在自己从未涉足过的行业或领域,现在需要自己去这个行业或领域学习探索,最初的目标是熟悉行业,快速融入进去,很多时候,我们只是了解了个大概,并没能…...
Sandbox: rsync.samba(80134) deny(1) file-write-create
Xcode15运行报错:Sandbox: rsync.samba(80134) deny(1) file-write-create/xxx/xxx 如下图: 解决办法: Build Settings 搜索 sandbox,把 Build Options 中的 User Script Sandboxing改为 NO...
lvs的dr模式综合实践
目录 编辑虚拟机准备工作 编辑编辑编辑 配置过程 配置client主机 配置router主机 配置lvs主机(vip使用环回来创建) 配置server1主机(vip使用环回来创建) 配置server2主机(vip使用环回来创建࿰…...
什么是自然语言处理
自然语言处理(Natural Language Processing, NLP)是人工智能(AI)的一个子领域,涉及计算机与人类语言的交互。它的目标是让计算机能够理解、分析、生成和操作自然语言,从而实现与人类的有效沟通。 自然语言处…...
快速理解互联网中的常用名词
并发与并行 并发:任务交替执行,伪并行,涉及CPU时间片和上下文切换。并行:任务真正同时执行,需要多核处理器,无上下文切换。 并发量(Concurrency) 概念:服务端程序单位…...
统计接口调用耗时_黑白名单配置
黑名单配置 黑名单就是那些被禁止访问的URL创建自定义过滤器 BlackListUrlFilter,并配置黑名单地址列表blacklistUrl如果有其他需求,还可以实现自定义规则的过滤器来满足特定的过滤要求 /*** 黑名单过滤器** author canghe*/ Component public class B…...
树莓派4 AV没有视频输出
使用AV接口输出,没有画面 需要在config.txt文件中 增加配置 enable_tvout1config.txt 中的 dtoverlayvc4-kms-v3d 行末尾添加,composite: dtoverlayvc4-kms-v3d,composite默认情况下,输出 NTSC 复合视频。要选择不同的模式,请在…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
