[Spring3.x源码]AOP(二)组装配置信息

AbstractApplicationContext context = new FileSystemXmlApplicationContext("beans.xml");这个过程不仅会解析普通的<bean>标签,<aop>标签也会被解析。

1.IoC在解析XML的时候,DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)中解析根节点的子节点。

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);
	}
}	

isDefaultNamespace()判断标签的命名空间是不是http://www.springframework.org/schema/beans,不是的话表示该标签为自定义标签,调用parseCustomElement解析节点。<aop>竟然也算是自定义标签。

delegate在DefaultBeanDefinitionDocumentReader.createHelper方法创建的。

BeanDefinitionParserDelegate.parseCustomElement(Element ele):

	public BeanDefinition parseCustomElement(Element ele) {
		return parseCustomElement(ele, null);
	}

	public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
		String namespaceUri = getNamespaceURI(ele);
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

readerContext是XmlReaderContext类的对象,是在DefaultBeanDefinitionDocumentReader中创建BeanDefinitionParserDelegate时,当构造函数的参数传入的。

this.delegate = createHelper(readerContext, root, parent);

protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {
	BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext, environment);
	delegate.initDefaults(root, parentDelegate);
	return delegate;
}
而DefaultBeanDefinitionDocumentReader的readerContext是在XmlBeanDefinitionReader.registerBeanDefinitions中创建DefaultBeanDefinitionDocumentReader的时候通过赋值的:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	//返回DefaultBeanDefinitionDocumentReader
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	documentReader.setEnvironment(this.getEnvironment());
	int countBefore = getRegistry().getBeanDefinitionCount();
	//createReaderContext(resource)就是readerContext的来源,返回XmlReaderContext对象
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
	return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}

private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
XmlBeanDefinitionReader.createReaderContext(resource)是readerContext的来源
protected XmlReaderContext createReaderContext(Resource resource) {
	if (this.namespaceHandlerResolver == null) {
		this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
	}
	return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
			this.sourceExtractor, this, this.namespaceHandlerResolver);
}
而这个this.namespaceHandlerResolver就是BeanDefinitionParserDelegate.parseCustomElement方法中this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);要调用的。
如果此对象为空,就创建一个createDefaultNamespaceHandlerResolver():

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
	return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}

2.回到BeanDefinitionParserDelegate.parseCustomElement中,DefaultNamespaceHandlerResolver.resolve(String namespaceUri),根据标签<aop:config>去取相关的命名空间,在根据虚拟的命名空间去取对应的解析类。

public NamespaceHandler resolve(String namespaceUri) {
	Map<String, Object> handlerMappings = getHandlerMappings();
	Object handlerOrClassName = handlerMappings.get(namespaceUri);
	if (handlerOrClassName == null) {
		return null;
	}
	else if (handlerOrClassName instanceof NamespaceHandler) {
		return (NamespaceHandler) handlerOrClassName;
	}
	else {
		String className = (String) handlerOrClassName;
		try {
			Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
			if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
				throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
						"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
			}
			NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
			namespaceHandler.init();
			handlerMappings.put(namespaceUri, namespaceHandler);
			return namespaceHandler;
		}
		catch (ClassNotFoundException ex) {
			throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
					namespaceUri + "] not found", ex);
		}
		catch (LinkageError err) {
			throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
					namespaceUri + "]: problem with handler class file or dependent class", err);
		}
	}
}

getHandlerMappings():

private Map<String, Object> getHandlerMappings() {
	if (this.handlerMappings == null) {
		synchronized (this) {
			if (this.handlerMappings == null) {
				try {
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
					if (logger.isDebugEnabled()) {
						logger.debug("Loaded NamespaceHandler mappings: " + mappings);
					}
					Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>();
					CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
					this.handlerMappings = handlerMappings;
				}
				catch (IOException ex) {
					throw new IllegalStateException(
							"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
				}
			}
		}
	}
	return this.handlerMappings;
}

PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);载入配置文件的信息,配置文件地址变量this.handlerMappingsLocation的来源:

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

private final String handlerMappingsLocation;

public DefaultNamespaceHandlerResolver() {
	this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
	this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
	Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
	this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
	this.handlerMappingsLocation = handlerMappingsLocation;
}

将所有包/META-INF/spring.handlers文件内容保存到Map类型的handlerMappings属性中。

AOP的标签<aop>对应的命名空间是http://www.springframework.org/schema/aop,在/spring-aop/src/main/resources/META-INF/spring.handlers这个文件中看看spring.handlers的内容:http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

这里将配置文件开头的命名空间虚拟路径和真实的类关联起来。

暂时回到DefaultNamespaceHandlerResolver.resolve(String namespaceUri)中,AopNamespaceHandler被创建时会执行init初始化操作:

	public void init() {
		// In 2.0 XSD as well as in 2.1 XSD.
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

		// Only in 2.0 XSD: moved to context namespace as of 2.1
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

3.将由这个AopNamespaceHandler类将<aop:config>的配置信息转为BeanDefinition

AopNamespaceHandler继承NamespaceHandlerSupport,

public BeanDefinition parse(Element element, ParserContext parserContext) {
	return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
	String localName = parserContext.getDelegate().getLocalName(element);
	BeanDefinitionParser parser = this.parsers.get(localName);
	if (parser == null) {
		parserContext.getReaderContext().fatal(
				"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
	}
	return parser;
}

返回<aop:config>标签对应的解析类,AopNamespaceHandler.init()方法中已经配置了——ConfigBeanDefinitionParser。

4.到了这里,spring才有解析<aop:config>的意思,ConfigBeanDefinitionParser.parse(Element element, ParseContext parserContext):

public BeanDefinition parse(Element element, ParserContext parserContext) {
	CompositeComponentDefinition compositeDef =
			new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
	parserContext.pushContainingComponent(compositeDef);

	configureAutoProxyCreator(parserContext, element);

	List<Element> childElts = DomUtils.getChildElements(element);
	for (Element elt: childElts) {
		String localName = parserContext.getDelegate().getLocalName(elt);
		if (POINTCUT.equals(localName)) {
			parsePointcut(elt, parserContext);
		}
		else if (ADVISOR.equals(localName)) {
			parseAdvisor(elt, parserContext);
		}
		else if (ASPECT.equals(localName)) {
			parseAspect(elt, parserContext);
		}
	}

	parserContext.popAndRegisterContainingComponent();
	return null;
}
configureAutoProxyCreator(parserContext, element);创建AspectJAwareAdvisorAutoProxyCreator实例,备用。

/**
	 * Configures the auto proxy creator needed to support the {@link BeanDefinition BeanDefinitions}
	 * created by the '<code><aop:config/></code>' tag. Will force class proxying if the
	 * '<code>proxy-target-class</code>' attribute is set to '<code>true</code>'.
	 * @see AopNamespaceUtils
	 */
	private void configureAutoProxyCreator(ParserContext parserContext, Element element) {
		AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
	}
AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary()

	public static void registerAspectJAutoProxyCreatorIfNecessary(
			ParserContext parserContext, Element sourceElement) {

		BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
				parserContext.getRegistry(), parserContext.extractSource(sourceElement));
		useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
		registerComponentIfNecessary(beanDefinition, parserContext);
	}
AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary

	public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
		return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
	}
	private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
			BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
			if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
				int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
				int requiredPriority = findPriorityForClass(cls);
				if (currentPriority < requiredPriority) {
					apcDefinition.setBeanClassName(cls.getName());
				}
			}
			return null;
		}
		RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
		beanDefinition.setSource(source);
		beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
		return beanDefinition;
	}
registry是DefaultListableBeanFactory的实例,registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); 将BeanDefinition保存到其beanDefinitionMap属性中。

在AbstractApplicationContext.refresh()中会调用registerBeanPostProcessors()方法将此bean实例化, 并调用beanFactory.addBeanPostProcessor()将其保存到AbstractBeanFactory的属性beanPostProcessors中。(AspectJAwareAdvisorAutoProxyCreator通过复杂继承关系算是实现了BeanPostProcessor接口)

5.开始解析<aop:aspect>标签ConfigBeanDefinitionParser.parseAspect():
private void parseAspect(Element aspectElement, ParserContext parserContext) {
	String aspectId = aspectElement.getAttribute(ID);
	String aspectName = aspectElement.getAttribute(REF);

	try {
		this.parseState.push(new AspectEntry(aspectId, aspectName));
		List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
		List<BeanReference> beanReferences = new ArrayList<BeanReference>();

		List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
		for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
			Element declareParentsElement = declareParents.get(i);
			beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
		}

		// We have to parse "advice" and all the advice kinds in one loop, to get the
		// ordering semantics right.
		NodeList nodeList = aspectElement.getChildNodes();
		boolean adviceFoundAlready = false;
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node node = nodeList.item(i);
			if (isAdviceNode(node, parserContext)) {
				if (!adviceFoundAlready) {
					adviceFoundAlready = true;
					if (!StringUtils.hasText(aspectName)) {
						parserContext.getReaderContext().error(
								"<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
								aspectElement, this.parseState.snapshot());
						return;
					}
					beanReferences.add(new RuntimeBeanReference(aspectName));
				}
				AbstractBeanDefinition advisorDefinition = parseAdvice(
						aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
				beanDefinitions.add(advisorDefinition);
			}
		}

		AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
				aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
		parserContext.pushContainingComponent(aspectComponentDefinition);

		List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
		for (Element pointcutElement : pointcuts) {
			parsePointcut(pointcutElement, parserContext);
		}

		parserContext.popAndRegisterContainingComponent();
	}
	finally {
		this.parseState.pop();
	}
}
for循环中的isAdviceNode(node, parserContext)做为过滤条件将只解析before、after、after-returning、after-throwing、around这样的子标签
/**
 * Return <code>true</code> if the supplied node describes an advice type. May be one of:
 * '<code>before</code>', '<code>after</code>', '<code>after-returning</code>',
 * '<code>after-throwing</code>' or '<code>around</code>'.
 */
private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
	if (!(aNode instanceof Element)) {
		return false;
	}
	else {
		String name = parserContext.getDelegate().getLocalName(aNode);
		return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) ||
				AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
	}
}
将aspectName=log,aspectElement=<aop:aspect>,adviceElement=<aop:after>...当参数传入parseAdvice,解析切面类。

/**
 * Parses one of '<code>before</code>', '<code>after</code>', '<code>after-returning</code>',
 * '<code>after-throwing</code>' or '<code>around</code>' and registers the resulting
 * BeanDefinition with the supplied BeanDefinitionRegistry.
 * @return the generated advice RootBeanDefinition
 */
private AbstractBeanDefinition parseAdvice(
		String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,
		List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {

	try {
		this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));

		// create the method factory bean
		RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
		methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
		methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
		methodDefinition.setSynthetic(true);

		// create instance factory definition
		RootBeanDefinition aspectFactoryDef =
				new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
		aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
		aspectFactoryDef.setSynthetic(true);

		// register the pointcut
		AbstractBeanDefinition adviceDef = createAdviceDefinition(
				adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
				beanDefinitions, beanReferences);

		// configure the advisor
		RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
		advisorDefinition.setSource(parserContext.extractSource(adviceElement));
		advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
		if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
			advisorDefinition.getPropertyValues().add(
					ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
		}

		// register the final advisor
		parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

		return advisorDefinition;
	}
	finally {
		this.parseState.pop();
	}
}
methodDefinition切面信息,包含切面类、方法
createAdviceDefinition()方法将切面信息、切入点信息的整合关联。advisor=advice+pointCut

/**
 * Creates the RootBeanDefinition for a POJO advice bean. Also causes pointcut
 * parsing to occur so that the pointcut may be associate with the advice bean.
 * This same pointcut is also configured as the pointcut for the enclosing
 * Advisor definition using the supplied MutablePropertyValues.
 */
private AbstractBeanDefinition createAdviceDefinition(
		Element adviceElement, ParserContext parserContext, String aspectName, int order,
		RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
		List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {

	RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
	adviceDefinition.setSource(parserContext.extractSource(adviceElement));

	adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);
	adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);

	if (adviceElement.hasAttribute(RETURNING)) {
		adviceDefinition.getPropertyValues().add(
				RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING));
	}
	if (adviceElement.hasAttribute(THROWING)) {
		adviceDefinition.getPropertyValues().add(
				THROWING_PROPERTY, adviceElement.getAttribute(THROWING));
	}
	if (adviceElement.hasAttribute(ARG_NAMES)) {
		adviceDefinition.getPropertyValues().add(
				ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES));
	}

	ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
	cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);

	Object pointcut = parsePointcutProperty(adviceElement, parserContext);
	if (pointcut instanceof BeanDefinition) {
		cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
		beanDefinitions.add((BeanDefinition) pointcut);
	}
	else if (pointcut instanceof String) {
		RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
		cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
		beanReferences.add(pointcutRef);
	}

	cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);

	return adviceDefinition;
}

RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));根据切面的类型(如before、after等)调取相应的类,创建切面类信息。

	private Class getAdviceClass(Element adviceElement, ParserContext parserContext) {
		String elementName = parserContext.getDelegate().getLocalName(adviceElement);
		if (BEFORE.equals(elementName)) {
			return AspectJMethodBeforeAdvice.class;
		}
		else if (AFTER.equals(elementName)) {
			return AspectJAfterAdvice.class;
		}
		else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {
			return AspectJAfterReturningAdvice.class;
		}
		else if (AFTER_THROWING_ELEMENT.equals(elementName)) {
			return AspectJAfterThrowingAdvice.class;
		}
		else if (AROUND.equals(elementName)) {
			return AspectJAroundAdvice.class;
		}
		else {
			throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
		}
	}
Object pointcut = parsePointcutProperty(adviceElement, parserContext);返回切入点信息(例子中,配置文件beans.xml中的servicesPointcut)。

/**
 * Parses the <code>pointcut</code> or <code>pointcut-ref</code> attributes of the supplied
 * {@link Element} and add a <code>pointcut</code> property as appropriate. Generates a
 * {@link org.springframework.beans.factory.config.BeanDefinition} for the pointcut if  necessary
 * and returns its bean name, otherwise returns the bean name of the referred pointcut.
 */
private Object parsePointcutProperty(Element element, ParserContext parserContext) {
	if (element.hasAttribute(POINTCUT) && element.hasAttribute(POINTCUT_REF)) {
		parserContext.getReaderContext().error(
				"Cannot define both 'pointcut' and 'pointcut-ref' on <advisor> tag.",
				element, this.parseState.snapshot());
		return null;
	}
	else if (element.hasAttribute(POINTCUT)) {
		// Create a pointcut for the anonymous pc and register it.
		String expression = element.getAttribute(POINTCUT);
		AbstractBeanDefinition pointcutDefinition = createPointcutDefinition(expression);
		pointcutDefinition.setSource(parserContext.extractSource(element));
		return pointcutDefinition;
	}
	else if (element.hasAttribute(POINTCUT_REF)) {
		String pointcutRef = element.getAttribute(POINTCUT_REF);
		if (!StringUtils.hasText(pointcutRef)) {
			parserContext.getReaderContext().error(
					"'pointcut-ref' attribute contains empty value.", element, this.parseState.snapshot());
			return null;
		}
		return pointcutRef;
	}
	else {
		parserContext.getReaderContext().error(
				"Must define one of 'pointcut' or 'pointcut-ref' on <advisor> tag.",
				element, this.parseState.snapshot());
		return null;
	}
}

advisor封装advice的信息(1:1),advice含有pointCut的信息(1:1)。

每个<aop:before>这种标签包含了类-方法信息,配置成一个advice。advisor是advice它的封装。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值