AOP 基本概念
OOP是自上而下从controller-service-dao-数据库。
AOP(Aspect-Oriented Programming)面向切面编程。AOP工作原理一句话概括:通过代理模式为目标对象生产代理对象,并将横切逻辑插入到目标方法执行的前后。
术语
切面(Aspect)
切面是切点和通知的集合,一般单独作为一个类。通知和切点共同定义了关于切面的全部内容。
白话文理解:影响了多个类的公共行为封装到一个可重用模块。
切点(PointCut)
切点是对一系列代表同种功能(目的)的切入点(连接点)的统称,切点不是一个点,而是代表某一功能的一系列连接点的集合。通常使用明确的类或者方法名称,或是利用正则表达式定义所匹配的类和方法来指定这些切点。
通知(Advice)
通知就是我们要在切点执行的操作,就是我们要实现的目的,是要实现的功能的代码实现。一般通知又称为增强。
通知有物种类型:
Before
在方法被调用之前调用After
在方法完成后调用通知,无论方法是否执行成功After-returning
在方法成功执行之后调用通知After-throwing
在方法抛出异常后调用通知Around
通知了好、包含了被通知的方法,在被通知的方法调用之前后调用之后执行自定义的行为
这里需要注意Around不是执行两次。如下代码,而是在pjp.proceed()方法前后的环绕。
@Around("controllerAspect()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
Object result = null;
Transaction transaction = null;
String uri = "";
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ServletContext ctx = request.getSession().getServletContext();
uri = request.getRequestURI().replace(ctx.getContextPath(), "").toLowerCase();
uri = uri.substring(uri.lastIndexOf("/") + 1);
if (uri.trim().matches("^\\d+$")) {
String numberUri = uri.trim();
uri = request.getRequestURI().replace(ctx.getContextPath(), "")
.replace("/", "").replace(numberUri, "")
.toLowerCase();
}
logger.info(String.format("controller path uri:%s", uri));
transaction = Cat.newTransaction(TYPE, uri);
Cat.logMetric(uri);
// 执行方法
result = pjp.proceed();
transaction.setStatus(Transaction.SUCCESS);
// 默认执行完方法不报错就是SUCCESS
Cat.logEvent(TYPE, uri, Event.SUCCESS, "");
} catch (Throwable e) {
logger.error("方法执行失败 controller path=" + uri, e);
transaction.setStatus(e);
// 默认执行完报错就记失败
Cat.logEvent(TYPE, uri, e.getMessage(), "");
throw e;
} finally {
transaction.complete();
}
logger.info("controller path={} spent {} seconds", uri, stopWatch.elapsedTime());
return result;
}
连接点(JoinPoint)
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时,抛出异常时,甚至修改一个字段时。切面代码可以利用这些点插入应用的正常流程中,并添加新的行为。Spring只支持方法的连接点
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入。
Spring AOP的切面织入是在运行时被织入,原理是使用了动态代理技术,Spring支持两种方式生产代理对象:JDK动态代理和CGLIB,默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理
引入(Introduction)
添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口。
目标对象(Target Object)
包含连接点的对象。也被称作被通知或被代理对象。
AOP代理(AOP Proxy)
AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
源码分析
**1、AOPNamespaceHandler中init是在什么时候调用的
2、对于XML配置AOP加载过程已了解,那么对于SpringBoot配置AOP加载过程呢。**
**XML配置AOP加载过程。
3、AOP有几种配置方式,每种配置方式,源码都是怎么走向。**
1、xml配置 <aop:config>标签使用分析
refresh
-> obtainFreshBeanFactory
-> refreshBeanFactory
-> AbstractRefreshableApplicationContext.loadBeanDefinitions(beanFactory)
-> ...
-> XmlBeanDefinitionReader.loadBeanDefinitions(Resource resource)
-> DefaultBeanDefinitionDocumentReader.parseBeanDefinitions
对于<aop:config>是自定义的标签不是defaultNamespace(默认是bean标签),那么走的是parseCustomElement方法
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);
}
}
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));
}
那么这个Handler就对应META/spring.handlers文件中。Spring各个jar包中的spring.handlers都会生效。
我们在看下NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);中的resolve方法,此方法拿到namespaceHandler之后会调用init方法。
@Override
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);
}
}
}
然后继续看parseCustomElement方法handler.parse方法,<aop:config>标签又交给了ConfigBeanDefinitionParser来解析.
首先findParseForElement获取parser然后进行调用parse方法。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
我们来看看ConfigBeanDefinitionParser的parse方法:大致是注册一个bean,以及切点、增强、切面的解析(pointcunt\advisor\aspect)
@Override
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);一路跟踪到如下
public static void registerAspectJAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
//注册一个AspectJAwareAdvisorAutoProxyCreator,
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
//解析配置元素,决定代理的模式
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
//作为系统组件,把这个creator这个bean,放到Spring容器中,让Spring实力化,启动这个Creator
registerComponentIfNecessary(beanDefinition, parserContext);
}
在bean实例化完成后,会调用BeanPostProcessor的postProcessAfterInitialization方法在其父类AbstractAutoProxyCreator中实现,
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
跟踪wrapIfNecessary方法,会进入到createProxy方法
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
// 意思就是如果该类有advice则创建proxy,
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 1.通过方法名也能简单猜测到,这个方法就是把bean包装为proxy的主要方法,
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
// 2.返回该proxy代替原来的bean
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
createProxy方法中最后一句proxyFactory.getProxy会进入到DefaultAopProxyFactory的createAopProxy方法。
protected Object createProxy(
Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
proxyFactory.addAdvisor(advisor);
}
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
//如果bean的类是接口或者类是JDK内部的代理类,则使用JDK的动态代理类。
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
//其他情况使用CGLIB来实现。
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
创建代理类后,其余过程与bean的生命周期基本一致。
2、注解配置 <aop:aspectj-autoproxy>标签使用分析
与aop:config标签大致类似,区别在于:
1、此标签交给AspectJAutoProxyBeanDefinitionParser去进行解析。
2、AspectJAutoProxyBeanDefinitionParser解析时候注册的是AnnotationAwareAspectJAutoProxyCreator的bean。
3、SpringBoot配置AOP
SpringBoot项目自动化配置是读取spring-boot-autoconfigure项目中的/META-INF/spring.factories的文件,以及json配置文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
{
"name": "spring.aop.auto",
"type": "java.lang.Boolean",
"description": "Add @EnableAspectJAutoProxy.",
"defaultValue": true
},
我们看下AopAutoConfiguration
@Configuration
//依赖于EnableAspectJAutoProxy与Aspect与Advice
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
//spring.aop.auto=true此类才会进行加载
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
//proxyTargetClass默认为false
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)
public static class CglibAutoProxyConfiguration {
}
}
那我们继续,来看EnableAspectJAutoProxy注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
}
导入了AspectJAutoProxyRegistrar配置我们来看AspectJAutoProxyRegistrar源码
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
这里是不是很熟悉,相当于<aop:aspectj-autoproxy>标签使用,注册AnnotationAwareAspectJAutoProxyCreator。springboot加载过程就是这样。
仔细想想SpringBoot是基于注解的形式,那么肯定是有什么地方调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
4、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());
}
一二行上面以及介绍过了,对于scoped-proxy可以理解为作用域代理。Spring中作用域默认是singleton.如果想使用其他除了单例模式以外的作用域,则需要添加scoped-proxy标签,默认走CGLIB代理。跟踪代码到ScopedProxyBeanDefinitionDecorator类中。
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
boolean proxyTargetClass = true;
if (node instanceof Element) {
Element ele = (Element) node;
if (ele.hasAttribute(PROXY_TARGET_CLASS)) {
proxyTargetClass = Boolean.valueOf(ele.getAttribute(PROXY_TARGET_CLASS));
}
}
// Register the original bean definition as it will be referenced by the scoped proxy
// and is relevant for tooling (validation, navigation).
BeanDefinitionHolder holder =
ScopedProxyUtils.createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass);
String targetBeanName = ScopedProxyUtils.getTargetBeanName(definition.getBeanName());
parserContext.getReaderContext().fireComponentRegistered(
new BeanComponentDefinition(definition.getBeanDefinition(), targetBeanName));
return holder;
}
再看其createScopedProxy方法
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
// Create a scoped proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition.
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
if (proxyTargetClass) {
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
}
else {
proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
}
// Copy autowire settings from original bean definition.
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
// The target bean should be ignored in favor of the scoped proxy.
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// Register the target bean as separate bean in the factory.
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// Return the scoped proxy definition as primary bean definition
// (potentially an inner bean).
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
对于创建ScopedProxyFactoryBean要看其中的setBeanFactory方法
@Override
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Class<?> beanType = beanFactory.getType(this.targetBeanName);
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +
"': Target type could not be determined at the time of proxy creation.");
}
if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
// Add an introduction that implements only the methods on ScopedObject.
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
// Add the AopInfrastructureBean marker to indicate that the scoped proxy
// itself is not subject to auto-proxying! Only its target bean is.
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
spring-configured不太了解就不多说了,有了解的可以一起交流。
好了写到这里,喜欢的大家互动起来。