概述
本文记录下看AOP源码的过程
回顾AOP
AOP是什么
- 面向切面编程
- Spring的两大特性之一
- 是OOP的重要补充
AOP有什么用
- 代码解耦
- 关注横向关系
- 分离核心业务和非核心业务
- 应用场景有:事务管理、缓存管理、日志收集、权限控制、性能监控等
AOP有哪些概念
- Aspect(切面) 哪些方法会被切中,切中后如何处理
- PointCut(切入点)定义哪些方法被切中,Java的AOP只支持方法,其他语言可以支持构造方法或属性
- JoinPoint(连接点)被切中的方法
- Advise(通知)在原有方法的基础上,加入新的功能
- 前置 before
- 后置 after
- 环绕 around
- 返回后置 afterReturning
- 异常后置 afterThrowing
- Weave(织入)在原有方法的功能上,加入新功能过程
AOP的实现原理
源码解析
解析源码需要带着目的来看,这次主要看两个重点:
- aop有关的标签是如何解析并注册到IOC容器中的
- 获得JavaBean时是如何产生动态代理的
接下来我们看看Spring源码中进行AOP的过程,首先我们要找到AOP的入口。
这不像IOC的源码有具体的类和方法可以直接进入,AOP是自动进行的,它是在哪里导入到Spring中的呢?
我们可以回想一下IOC的过程:
有一个步骤是分别解析默认的命名空间和自定义的命名空间,aop就属于自定义命名空间,会不会在这里Spring做了特殊的处理?
我们从这个位置进入看看:
DefaultBeanDefinitionDocumentReader类:
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)) {
//解析默认命名空间
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
//解析自定义命名空间
delegate.parseCustomElement(root);
}
}
进入解析自定义命名空间的方法,首先是拿到元素的URI,对于AOP就是http://www.springframework.org/schema/aop,然后通过URI找到对应的命名空间处理器NamespaceHandler,对应AOP的处理器就是AOPNamespaceHandler,然后由处理器来进行解析。
BeanDefinitionParserDelegate类:
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//获得Element的URI
String namespaceUri = this.getNamespaceURI(ele);
//通过URI找到处理器
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
} else {
//解析XML元素
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
}
看下AopNamespaceHandler类
public class AopNamespaceHandler extends NamespaceHandlerSupport {
public AopNamespaceHandler() {
}
public void init() {
this.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
this.registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
this.registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
它实际上就是注册了四个解析器来处理四种aop标签:
- config ConfigBeanDefinitionParser
- aspectj-autoproxy AspectJAutoProxyBeanDefinitionParser
- scoped-proxy ScopedProxyBeanDefinitionDecorator
- spring-configured SpringConfiguredBeanDefinitionParser
我们最常用的是aop:config标签,接下来我们看看ConfigBeanDefinitionParser类
这个类中主要有个parse方法,第一个参数是XML元素,第二个是解析器上下文
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
this.configureAutoProxyCreator(parserContext, element);
List<Element> childElts = DomUtils.getChildElements(element);
Iterator var5 = childElts.iterator();
while(var5.hasNext()) {
Element elt = (Element)var5.next();
String localName = parserContext.getDelegate().getLocalName(elt);
if ("pointcut".equals(localName)) {
this.parsePointcut(elt, parserContext);
} else if ("advisor".equals(localName)) {
this.parseAdvisor(elt, parserContext);
} else if ("aspect".equals(localName)) {
this.parseAspect(elt, parserContext);
}
}
parserContext.popAndRegisterContainingComponent();
return null;
}
下面部分通过标签名称的判断,解析config标签中的pointcut、advisor和aspect这三个标签。先看看切面部分的解析:
private void parseAspect(Element aspectElement, ParserContext parserContext) {
//获得aspect的id和name值
String aspectId = aspectElement.getAttribute("id");
String aspectName = aspectElement.getAttribute("ref");
try {
this.parseState.push(new AspectEntry(aspectId, aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList();
List<BeanReference> beanReferences = new ArrayList();
//获得已知的父类元素
List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, "declare-parents");
//解析所有的父类元素
for(int i = 0; i < declareParents.size(); ++i) {
Element declareParentsElement = (Element)declareParents.get(i);
beanDefinitions.add(this.parseDeclareParents(declareParentsElement, parserContext));
}
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
//遍历所有子节点
for(int i = 0; i < nodeList.getLength(); ++i) {
Node node = nodeList.item(i);
//判断是否是before、after、after-returning、after-throwing、around
if (this.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));
}
//解析每个advice,并添加到集合中
AbstractBeanDefinition advisorDefinition = this.parseAdvice(aspectName, i, aspectElement, (Element)node, parserContext, beanDefinitions, beanReferences);
beanDefinitions.add(advisorDefinition);
}
}
AspectComponentDefinition aspectComponentDefinition = this.createAspectComponentDefinition(aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
parserContext.pushContainingComponent(aspectComponentDefinition);
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, "pointcut");
Iterator var21 = pointcuts.iterator();
//解析aspect中所有的pointcut子节点
while(var21.hasNext()) {
Element pointcutElement = (Element)var21.next();
this.parsePointcut(pointcutElement, parserContext);
}
//发送注册完成的事件
parserContext.popAndRegisterContainingComponent();
} finally {
this.parseState.pop();
}
}
在看看Advice的解析:
private AbstractBeanDefinition parseAdvice(String aspectName, int order,
Element aspectElement, Element adviceElement, ParserContext parserContext, List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
RootBeanDefinition var12;
try {
//入栈一个解析状态
this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));
//创建BeanDefinition,保存方法的信息
RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
methodDefinition.setSynthetic(true);
//创建BeanDefinition,保存工厂信息
RootBeanDefinition aspectFactoryDef = new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
aspectFactoryDef.setSynthetic(true);
//把前面解析出来的内容包装起来,创建一个Advise方法的BeanDefinition
AbstractBeanDefinition adviceDef = this.createAdviceDefinition(adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef, beanDefinitions, beanReferences);
//所有内容最后放在这个BeanDefinition中
RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
advisorDefinition.setSource(parserContext.extractSource(adviceElement));
//将Advice的定义添加到Advisor的构造方法参数中
advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
//解析aspect的order属性
if (aspectElement.hasAttribute("order")) {
advisorDefinition.getPropertyValues().add("order", aspectElement.getAttribute("order"));
}
//在IOC容器中注册Advisor的BeanDefinition
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
var12 = advisorDefinition;
} finally {
this.parseState.pop();
}
return var12;
}
创建Advise定义的方法,这个方法把前面解析的一系列内容放到BeanDefinition中。
private AbstractBeanDefinition createAdviceDefinition(Element adviceElement, ParserContext parserContext, String aspectName, int order, RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef, List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
//获得Advise的类型,并创建一个BeanDefinition
RootBeanDefinition adviceDefinition = new RootBeanDefinition(this.getAdviceClass(adviceElement, parserContext));
adviceDefinition.setSource(parserContext.extractSource(adviceElement));
adviceDefinition.getPropertyValues().add("aspectName", aspectName);
adviceDefinition.getPropertyValues().add("declarationOrder", order);
if (adviceElement.hasAttribute("returning")) {
adviceDefinition.getPropertyValues().add("returningName", adviceElement.getAttribute("returning"));
}
if (adviceElement.hasAttribute("throwing")) {
adviceDefinition.getPropertyValues().add("throwingName", adviceElement.getAttribute("throwing"));
}
if (adviceElement.hasAttribute("arg-names")) {
adviceDefinition.getPropertyValues().add("argumentNames", adviceElement.getAttribute("arg-names"));
}
ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
cav.addIndexedArgumentValue(0, methodDef);
Object pointcut = this.parsePointcutProperty(adviceElement, parserContext);
if (pointcut instanceof BeanDefinition) {
cav.addIndexedArgumentValue(1, pointcut);
beanDefinitions.add((BeanDefinition)pointcut);
} else if (pointcut instanceof String) {
RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String)pointcut);
cav.addIndexedArgumentValue(1, pointcutRef);
beanReferences.add(pointcutRef);
}
cav.addIndexedArgumentValue(2, aspectFactoryDef);
return adviceDefinition;
}
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".equals(elementName)) {
return AspectJAfterReturningAdvice.class;
} else if ("after-throwing".equals(elementName)) {
return AspectJAfterThrowingAdvice.class;
} else if ("around".equals(elementName)) {
return AspectJAroundAdvice.class;
} else {
throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
}
}
到这里aop:config标签里的所有内容都被解析出来了,包装成BeanDefinition,并注册到IOC的容器中了。
再看动态代理是如何产生的,所有的Bean包括代理,都可以通过getBean来获得,我们从这条线索跟下去:
AbstractApplicationContext类:
public Object getBean(String name) throws BeansException {
this.assertBeanFactoryActive();
//这里
return this.getBeanFactory().getBean(name);
}
AbstractBeanFactory类:
public Object getBean(String name) throws BeansException {
//这里
return this.doGetBean(name, (Class)null, (Object[])null, false);
}
跟进去
protected <T> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = this.transformedBeanName(name);
Object sharedInstance = this.getSingleton(beanName);
Object bean;
if (sharedInstance != null && args == null) {
if (this.logger.isDebugEnabled()) {
if (this.isSingletonCurrentlyInCreation(beanName)) {
this.logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
} else {
this.logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
//这里 获得JavaBean实例对象
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
} else {
...
}
}
跟进去
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(this.transformedBeanName(name), beanInstance.getClass());
} else if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) {
Object object = null;
if (mbd == null) {
object = this.getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
FactoryBean<?> factory = (FactoryBean)beanInstance;
if (mbd == null && this.containsBeanDefinition(beanName)) {
mbd = this.getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = mbd != null && mbd.isSynthetic();
//这里 通过工厂Bean获得对象
object = this.getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
} else {
return beanInstance;
}
}
跟进去
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(this.transformedBeanName(name), beanInstance.getClass());
} else if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) {
Object object = null;
if (mbd == null) {
object = this.getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
FactoryBean<?> factory = (FactoryBean)beanInstance;
if (mbd == null && this.containsBeanDefinition(beanName)) {
mbd = this.getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = mbd != null && mbd.isSynthetic();
//这里
object = this.getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
} else {
return beanInstance;
}
}
跟进去
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
if (factory.isSingleton() && this.containsSingleton(beanName)) {
synchronized(this.getSingletonMutex()) {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
// 这里
object = this.doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
} else {
if (object != null && shouldPostProcess) {
try {
object = this.postProcessObjectFromFactoryBean(object, beanName);
} catch (Throwable var9) {
throw new BeanCreationException(beanName, "Post-processing of FactoryBean's singleton object failed", var9);
}
}
this.factoryBeanObjectCache.put(beanName, object != null ? object : NULL_OBJECT);
}
}
return object != NULL_OBJECT ? object : null;
}
} else {
Object object = this.doGetObjectFromFactoryBean(factory, beanName);
if (object != null && shouldPostProcess) {
try {
object = this.postProcessObjectFromFactoryBean(object, beanName);
} catch (Throwable var11) {
throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", var11);
}
}
return object;
}
}
跟进去
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, String beanName) throws BeanCreationException {
Object object;
try {
if (System.getSecurityManager() != null) {
AccessControlContext acc = this.getAccessControlContext();
try {
object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
return factory.getObject();
}
}, acc);
} catch (PrivilegedActionException var6) {
throw var6.getException();
}
} else {
//这里
object = factory.getObject();
}
} catch (FactoryBeanNotInitializedException var7) {
throw new BeanCurrentlyInCreationException(beanName, var7.toString());
} catch (Throwable var8) {
throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", var8);
}
if (object == null && this.isSingletonCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject");
} else {
return object;
}
}
这个getObject就是从工厂中拿到对象了,FactoryBean有很多种实现类,我们往下跟ProxyFactoryBean也就是代理的工厂Bean。
ProxyFactoryBean
public Object getObject() throws BeansException {
this.initializeAdvisorChain();
if (this.isSingleton()) {
//生产单例对象
return this.getSingletonInstance();
} else {
if (this.targetName == null) {
this.logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
}
//创建多例对象
return this.newPrototypeInstance();
}
}
看看如何获得单例对象的
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = this.freshTargetSource();
//还没有获得接口
if (this.autodetectInterfaces && this.getProxiedInterfaces().length == 0 && !this.isProxyTargetClass()) {
Class<?> targetClass = this.getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
//给代理类设置接口
this.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
super.setFrozen(this.freezeProxy);
//创建AOP代理
this.singletonInstance = this.getProxy(this.createAopProxy());
}
return this.singletonInstance;
}
ProxyCreatorSupport
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
this.activate();
}
return this.getAopProxyFactory().createAopProxy(this);
}
DefaultAopProxyFactory
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
//如果当前配置不需要进一步优化,也不是代理目标类,存在可用的接口
if (!config.isOptimize() && !config.isProxyTargetClass()
&& !this.hasNoUserSuppliedProxyInterfaces(config)) {
//创建JDK动态代理
return new JdkDynamicAopProxy(config);
} else {
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.");
} else {
//如果目标类没有实现接口就创建CGLIB代理,否则创建JDK代理
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
JDK代理的实现
class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
this.findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;
Class var10;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
Boolean var20 = this.equals(args[0]);
return var20;
}
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
Integer var18 = this.hashCode();
return var18;
}
if (method.getDeclaringClass() != DecoratingProxy.class) {
Object retVal;
if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
return retVal;
}
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
//如果拦截器链为空,也就是before、after这些没有配置
//就直接调用目标对象的方法
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} else {
//如果配置了before、after这些操作,就按照顺序调用这些方法
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
retVal = proxy;
} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
}
Object var13 = retVal;
return var13;
}
var10 = AopProxyUtils.ultimateTargetClass(this.advised);
} finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
return var10;
}
ReflectiveMethodInvocation
public Object proceed() throws Throwable {
//当前拦截器位置已经到拦截器链的末尾或拦截器链为空
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
//调用自身方法
return this.invokeJoinpoint();
} else {
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
//进行方法和拦截器匹配,匹配成功就调用拦截器的方法,否则递归调用下一个拦截器
return dm.methodMatcher.matches(this.method, this.targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();
} else {
return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
}
}
}
总结
这样AOP主要的源码实现我们都看到了,主要的过程是:
- 解析配置文件时,将AOP有关配置解析出来,注册到IOC容器中
- 从IOC容器获得JavaBean时,会进行判断
- 如果JavaBean存在AOP代理,就通过ProxyFactoryBean类来生成AOP的动态代理
- 否则就生成CGLIB的动态代理
- 然后通过Spring中配置的方法拦截链调用各种拦截方法实现AOP。
今天我们从基本概念和实现,原理和源码角度来剖析了Spring AOP的实现,AOP是Spring非常重要的组成部分,在许多业务中可以给我们提供通用的功能,今天的课程可以帮助大家更加深入理解AOP。
如果你坚持看完了,给自己点个赞吧:)
大家如果需要学习其他Java知识点,戳这里 超详细的Java知识点汇总