前言

这里先说一点题外话。由于明年一月份火焰纹章系列新作-火焰纹章Engage就要发售了,我作为一个老火纹厨表示,这个2022年真的一秒钟也待不下去了(恼)。所以就用了这么个标题。也算是强行契合了这次的主题。Engage,有结合的意思。我们上一章用动态代理,实现了AOP的核心功能。但是这个功能的实现相对比较独立,和前面的内容有很强的割裂感。那我们自然是不能满足于这样的现状的,直接把这样的内容交给用户,是很难使用的。我们需要把它和Spring的生命周期结合起来,以配置的方式,达到润物细无声的效果。さあ、私と、Engage!(快到2023年吧~

工程结构

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─akitsuki
│  │  │          └─springframework
│  │  │              ├─aop
│  │  │              │  │  AdvisedSupport.java
│  │  │              │  │  Advisor.java
│  │  │              │  │  BeforeAdvice.java
│  │  │              │  │  ClassFilter.java
│  │  │              │  │  MethodBeforeAdvice.java
│  │  │              │  │  MethodMatcher.java
│  │  │              │  │  Pointcut.java
│  │  │              │  │  PointcutAdvisor.java
│  │  │              │  │  TargetSource.java
│  │  │              │  │
│  │  │              │  ├─aspect
│  │  │              │  │      AspectJExpressionPointcut.java
│  │  │              │  │      AspectJExpressionPointcutAdvisor.java
│  │  │              │  │
│  │  │              │  └─framework
│  │  │              │      │  AopProxy.java
│  │  │              │      │  Cglib2AopProxy.java
│  │  │              │      │  JdkDynamicAopProxy.java
│  │  │              │      │  ProxyFactory.java
│  │  │              │      │  ReflectiveMethodInvocation.java
│  │  │              │      │
│  │  │              │      ├─adapter
│  │  │              │      │      MethodBeforeAdviceInterceptor.java
│  │  │              │      │
│  │  │              │      └─autoproxy
│  │  │              │              DefaultAdvisorAutoProxyCreator.java
│  │  │              │
│  │  │              ├─beans
│  │  │              │  ├─exception
│  │  │              │  │      BeanException.java
│  │  │              │  │
│  │  │              │  └─factory
│  │  │              │      │  Aware.java
│  │  │              │      │  BeanClassLoaderAware.java
│  │  │              │      │  BeanFactory.java
│  │  │              │      │  BeanFactoryAware.java
│  │  │              │      │  BeanNameAware.java
│  │  │              │      │  ConfigurableListableBeanFactory.java
│  │  │              │      │  DisposableBean.java
│  │  │              │      │  FactoryBean.java
│  │  │              │      │  HierarchicalBeanFactory.java
│  │  │              │      │  InitializingBean.java
│  │  │              │      │  ListableBeanFactory.java
│  │  │              │      │
│  │  │              │      ├─config
│  │  │              │      │      AutowireCapableBeanFactory.java
│  │  │              │      │      BeanDefinition.java
│  │  │              │      │      BeanDefinitionRegistryPostProcessor.java
│  │  │              │      │      BeanFactoryPostProcessor.java
│  │  │              │      │      BeanPostProcessor.java
│  │  │              │      │      BeanReference.java
│  │  │              │      │      ConfigurableBeanFactory.java
│  │  │              │      │      DefaultSingletonBeanRegistry.java
│  │  │              │      │      InstantiationAwareBeanPostProcessor.java
│  │  │              │      │      PropertyValue.java
│  │  │              │      │      PropertyValues.java
│  │  │              │      │      SingletonBeanRegistry.java
│  │  │              │      │
│  │  │              │      ├─support
│  │  │              │      │      AbstractAutowireCapableBeanFactory.java
│  │  │              │      │      AbstractBeanDefinitionReader.java
│  │  │              │      │      AbstractBeanFactory.java
│  │  │              │      │      BeanDefinitionReader.java
│  │  │              │      │      BeanDefinitionRegistry.java
│  │  │              │      │      CglibSubclassingInstantiationStrategy.java
│  │  │              │      │      DefaultListableBeanFactory.java
│  │  │              │      │      DisposableBeanAdapter.java
│  │  │              │      │      FactoryBeanRegistrySupport.java
│  │  │              │      │      InstantiationStrategy.java
│  │  │              │      │      SimpleInstantiationStrategy.java
│  │  │              │      │
│  │  │              │      └─xml
│  │  │              │              XmlBeanDefinitionReader.java
│  │  │              │
│  │  │              ├─context
│  │  │              │  │  ApplicationContext.java
│  │  │              │  │  ApplicationContextAware.java
│  │  │              │  │  ApplicationEvent.java
│  │  │              │  │  ApplicationEventPublisher.java
│  │  │              │  │  ApplicationListener.java
│  │  │              │  │  ConfigurableApplicationContext.java
│  │  │              │  │
│  │  │              │  ├─event
│  │  │              │  │      AbstractApplicationEventMulticaster.java
│  │  │              │  │      ApplicationContextEvent.java
│  │  │              │  │      ApplicationEventMulticaster.java
│  │  │              │  │      ContextClosedEvent.java
│  │  │              │  │      ContextRefreshEvent.java
│  │  │              │  │      SimpleApplicationEventMulticaster.java
│  │  │              │  │
│  │  │              │  └─support
│  │  │              │          AbstractApplicationContext.java
│  │  │              │          AbstractRefreshableApplicationContext.java
│  │  │              │          AbstractXmlApplicationContext.java
│  │  │              │          ApplicationContextAwareProcessor.java
│  │  │              │          ClasspathXmlApplicationContext.java
│  │  │              │
│  │  │              ├─core
│  │  │              │  └─io
│  │  │              │          ClasspathResource.java
│  │  │              │          DefaultResourceLoader.java
│  │  │              │          FileSystemResource.java
│  │  │              │          Resource.java
│  │  │              │          ResourceLoader.java
│  │  │              │          UrlResource.java
│  │  │              │
│  │  │              └─util
│  │  │                      ClassUtils.java
│  │  │
│  │  └─resources
│  └─test
│      ├─java
│      │  └─com
│      │      └─akitsuki
│      │          └─springframework
│      │              │  ApiTest.java
│      │              │
│      │              └─bean
│      │                      IUserDao.java
│      │                      UserDao.java
│      │                      UserDaoBeforeAdvice.java
│      │
│      └─resources
│              spring.xml

Advice:收下我的建议吧!

我们讨论AOP,就不能不讨论Advice。如果我们通览一遍代码的话,会发现与Advice相关的内容非常多。那么到底什么是Advice呢?这里的Advice,可以被解释为【通知】。我们知道,实际上AOP就是我们对原有bean的一系列增强,那么Advice就是我们要增强的逻辑。当然就我个人而言,我也倾向于把它感性地解释为【建议】,我给你的建议,就是对你功能的增强,这样去理解。那么接下来,我们就来完成AOP中的Advice系列内容。

这次我们准备做一个方法执行前的切面,所以我们需要有一个BeforeAdvice。

package com.akitsuki.springframework.aop;import org.aopalliance.aop.Advice;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 14:52*/
public interface BeforeAdvice extends Advice {}

可以看到,它继承了一个标记接口Advice,代表我们的BeforeAdvice,也是一个Advice。那么我们还需要更加具体的去描述它,具体到哪里呢?方法。

package com.akitsuki.springframework.aop;import java.lang.reflect.Method;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 15:00*/
public interface MethodBeforeAdvice extends BeforeAdvice {void before(Method method, Object[] args, Object target) throws Throwable;
}

这次我们有了一个方法,before。那么我们将要实现的before方法,就是我们要对真正bean要执行的方法,进行的增强内容了。我们在调用真正的bean方法之前,这里的before会先执行。

有了【建议】,那么提出建议的人是谁?自然是【建议者】了

package com.akitsuki.springframework.aop;import org.aopalliance.aop.Advice;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 14:56*/
public interface Advisor {Advice getAdvice();
}

可以看到我们通过getAdvice方法,就可以获取到对应的【建议】。我们接下来,还要对这个概念进行细分。建议者,是谁的建议者呢?是【切点】的建议者

package com.akitsuki.springframework.aop;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 15:02*/
public interface PointcutAdvisor extends Advisor {Pointcut getPointcut();
}

我们上一章中也介绍了 Pointcut,切点。它提供了两个方法,分别是获取类过滤器和方法匹配器。通过这两个方法,我们也可以看出,它的作用是用来判断【应该切谁】这件事情。通过类和方法的筛选,我们就可以确定到一个具体的对象了。

那么有了这么个接口,我们自然需要一个类去实现它,来让它更加具体。我们这次的实现,都是用AspectJ表达式来进行的,所以我们需要一个基于AspectJ的实现类,来实现这个接口。

package com.akitsuki.springframework.aop.aspect;import com.akitsuki.springframework.aop.Pointcut;
import com.akitsuki.springframework.aop.PointcutAdvisor;
import org.aopalliance.aop.Advice;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 15:06*/
public class AspectJExpressionPointcutAdvisor implements PointcutAdvisor {/*** 切面*/private AspectJExpressionPointcut pointcut;/*** 具体的拦截方法*/private Advice advice;/*** 表达式*/private String expression;public void setExpression(String expression) {this.expression = expression;}public void setAdvice(Advice advice) {this.advice = advice;}@Overridepublic Advice getAdvice() {return advice;}@Overridepublic Pointcut getPointcut() {if (null == pointcut) {pointcut = new AspectJExpressionPointcut(expression);}return pointcut;}
}

这里有一些我们的老朋友,AspectJExpressionPointcut是我们上一章实现的,它的主要内容是对类过滤器和方法匹配器的管理和使用,用它来判断一个bean是否应该被当前表达式进行切入。expression则是我们的表达式,用来指定要切的位置的属性。

MethodInterceptor:站住别动

MethodInterceptor,方法拦截器。可以把即将要执行的方法拦截下来,在需要的时候再去执行(当然,你也可以不让它执行)。对于我们的Before切面来说,我们需要在方法执行前,执行我们的切面逻辑,然后再执行方法的正常逻辑。所以,我们需要一个Before的方法拦截器。

package com.akitsuki.springframework.aop.framework.adapter;import com.akitsuki.springframework.aop.MethodBeforeAdvice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 15:15*/
public class MethodBeforeAdviceInterceptor implements MethodInterceptor {private MethodBeforeAdvice advice;public  MethodBeforeAdviceInterceptor() {}public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {this.advice = advice;}@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {this.advice.before(methodInvocation.getMethod(), methodInvocation.getArguments(), methodInvocation.getThis());return methodInvocation.proceed();}
}

可以看到,这里的逻辑,就是在方法执行前,调用了advice的before方法,执行了切面逻辑。

ProxyFactory:就让我来生产代理!

我们知道,实现代理有多种方式,所以我们需要一个工厂来帮我们生产对应的代理对象。

package com.akitsuki.springframework.aop.framework;import com.akitsuki.springframework.aop.AdvisedSupport;
import lombok.AllArgsConstructor;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 15:19*/
@AllArgsConstructor
public class ProxyFactory {private AdvisedSupport advisedSupport;public Object getProxy() {return createProxy().getProxy();}private AopProxy createProxy() {if (advisedSupport.isProxyTargetClass()) {return new Cglib2AopProxy(advisedSupport);}return new JdkDynamicAopProxy(advisedSupport);}
}

可以看到,我们具体使用什么方法来生产代理对象,是取决于 adviceSupportisProxyTargetClass 的。通过true和false两种状态,来选择 cglibjdk两种方式。我们在使用时,只需要调用 getProxy方法,就可以获得最终的被代理后的对象了。

Engage!让我与你结合!

写到这儿,都快结束了,终于扯到我们的主题了:如何与Spring的生命周期结合起来。我们知道,Spring的核心是Bean。我们生产的代理对象,终究也是Bean。而且,我们的这些Advice、Interceptor、Advisor之类的,也都需要成为一个Bean,才能够方便Spring进行管理。

但是仅仅作为Bean还不够,我们还需要将这个过程变得自动化。让Spring初始化完成这些Bean之后,我们的这一系列功能就顺带着被搞定了。所以这里就要用到我们前面几章学习到的工具:后置处理器PostProcessor。既然要在初始化Bean时候的完成,那么我们自然就要在这一步,加入处理器,来帮我们搞定AOP的一系列内容。

我们需要两样东西:一个处理类,一个后置处理器。处理类是真正干活的,处理器则是用来插入Bean创建过程的。这里我们需要新加入一种后置处理器,它在Bean创建的最开始进行工作,它用于提供一种特殊的Bean实例化过程,通过这种过程,Bean的实例化不需要走默认的那套实例化。

package com.akitsuki.springframework.beans.factory.config;import com.akitsuki.springframework.beans.exception.BeanException;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 15:46*/
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {/*** bean实例化之前处理* @param beanClass* @param beanName* @return* @throws BeanException*/Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeanException;
}

有了这么一个后置处理器,我们就来实现真正的处理类吧。

package com.akitsuki.springframework.aop.framework.autoproxy;import com.akitsuki.springframework.aop.*;
import com.akitsuki.springframework.aop.aspect.AspectJExpressionPointcutAdvisor;
import com.akitsuki.springframework.aop.framework.ProxyFactory;
import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.BeanFactory;
import com.akitsuki.springframework.beans.factory.BeanFactoryAware;
import com.akitsuki.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import com.akitsuki.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;import java.util.Collection;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 15:31*/
public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {private DefaultListableBeanFactory beanFactory;private boolean isInfrastructureClass(Class<?> beanClass) {return Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass);}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeanException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeanException {return bean;}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeanException {if (isInfrastructureClass(beanClass)) {return null;}Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();for (AspectJExpressionPointcutAdvisor advisor : advisors) {ClassFilter classFilter = advisor.getPointcut().getClassFilter();if (!classFilter.matches(beanClass)) {continue;}AdvisedSupport advisedSupport = new AdvisedSupport();TargetSource targetSource = null;try {targetSource = new TargetSource(beanClass.getDeclaredConstructor().newInstance());} catch (Exception e) {e.printStackTrace();}advisedSupport.setTargetSource(targetSource);advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());advisedSupport.setProxyTargetClass(false);return new ProxyFactory(advisedSupport).getProxy();}return null;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeanException {this.beanFactory = (DefaultListableBeanFactory) beanFactory;}
}

好长好长,我们从上往下分析。

首先我们看到,这个类实现了我们上面的那个处理器,还有BeanFactoryAware接口,以便将BeanFactory注入进来。再往下是一个私有的判断方法,判断是否为这几种AOP中的基本类型。然后由于 InstantiationAwareBeanPostProcessor继承了 BeanPostProcessor,所以也有bean实例化前后的处理方法。不过在这里我们用不到,所以直接返回即可,我们的重头戏在 postProcessBeforeInstantiation的实现。

在方法的开头,就对这些AOP的基本类型进行了判断,如果是这些基本类型,是不走代理的,所以直接返回。接下来是对所有Advisor的一个遍历,主要是为了找到一个满足当前Bean的class的一个Advisor,然后构造出一个AdvisedSupport,将TargetSource、方法拦截器、方法匹配器、代理类型等数据给封装进去。这里需要注意的是,真实对象的创建,是在构建TargetSource的时候,通过反射进行的。这里进行了一些简化处理,只调用了无参的构造方法,实际上这里应该更加完善。最后,新建一个代理工厂,生产出最终的代理对象进行返回,我们的处理就算完成了。

我们要做的最后一步,就是进行织入,结合,Engage!因为我们虽然有了后置处理器,但我们的这个后置处理器实际上还没有发挥作用,我们要将其融入Bean的创建过程中。所以我们还需要回到我们的老朋友 AbstractAutowireCapableBeanFactory那里。

package com.akitsuki.springframework.beans.factory.support;/*** 抽象的实例化Bean类,提供创建Bean的能力,创建完成后,放入单例bean map中进行缓存** @author ziling.wang@hand-china.com* @date 2022/11/7 10:12*/
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {@Overrideprotected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeanException {Object bean;try {bean = resolveBeforeInstantiation(beanName, beanDefinition);if (null != bean) {return bean;}bean = createBeanInstance(beanDefinition, beanName, args);//设置bean属性applyPropertyValues(beanName, beanDefinition, bean);//初始化bean,执行beanPostProcessor的前置和后置方法initializeBean(beanName, bean, beanDefinition);//注册实现了DisposableBean接口的对象registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);if (beanDefinition.isSingleton()) {//创建好的单例bean,放入缓存addSingleton(beanName, bean);}} catch (Exception e) {throw new BeanException("创建bean失败", e);}return bean;}protected Object resolveBeforeInstantiation(String beanName, BeanDefinition beanDefinition) {Object bean = applyBeanPostProcessorsBeforeInstantiation(beanDefinition.getBeanClass(), beanName);if (null != bean) {bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);}return bean;}protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {for (BeanPostProcessor processor : getBeanPostProcessors()) {if (processor instanceof InstantiationAwareBeanPostProcessor) {Object result = ((InstantiationAwareBeanPostProcessor) processor).postProcessBeforeInstantiation(beanClass, beanName);if (null != result) {return result;}}}return null;}
}

这里我做了一些精简,只保留了有变化的方法,毕竟这个类经过多次扩充完善,已经变得越来越庞大了。

我们首先看织入点:createBean方法。这里我们其实只需要看前几行就可以了。resolveBeforeInstantiation是我们要做事情的方法,如果这个方法成功返回了一个Bean,那么就可以直接返回了,不需要走下面的那一套标准的Bean创建流程。这就是我们上面说的,提供一个特殊的Bean创建流程。那接下来我们看 resolveBeforeInstantiation的内容。它的内容也很简单,是调用 applyBeanPostProcessorsBeforeInstantiation,同样的,如果成功返回了一个Bean,则去调用实例化完成后置处理器,就完成了。看来我们的真正逻辑,还得跟踪到 applyBeanPostProcessorsBeforeInstantiation中。去看一眼,原来如此,是拿到了所有的Bean后置处理器,然后挑出来 InstantiationAwareBeanPostProcessor类型的,去挨个调用处理方法。如果有一个方法返回的结果不为空(也就意味着成功生产出了Bean),就返回。否则,继续找,直到找到为止,或者循环结束,最后返回null。结合我们上面的处理,可以看出,这里如果返回了null,就会走正常的bean创建逻辑。那么我们的这个处理器的逻辑就算织入完毕了。

测试!我们的结合必将如利剑般撕破黑暗

这个系列写到现在,唯一不变的可能就是各种意义不明的标题了。无奈作者我是一个中二少年(少年?),就算成为了码农,还是会时不时泄露出来一些中二气息(笑。

好了,我们来看测试。上次测试中的接口和Dao,我们不需要修改。这次我们需要实现一个MethodBeforeAdvice,作为我们的切面处理。

package com.akitsuki.springframework.bean;import com.akitsuki.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;/*** @author ziling.wang@hand-china.com* @date 2022/12/7 10:09*/
public class UserDaoBeforeAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println("方法执行了:" + method);}
}

实现了MethodBeforeAdvice接口,并且实现了before方法。可以推断,当方法被拦截下来之后,这里的before方法就会先执行,打印出相应的方法名。

然后,这次我们的配置文件,终于有用武之地了。我已经等不及了,快点端上来罢(无端联想

<beans><bean id="userDao" class="com.akitsuki.springframework.bean.UserDao"/><bean class="com.akitsuki.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/><bean id="beforeAdvice" class="com.akitsuki.springframework.bean.UserDaoBeforeAdvice"/><bean id="methodInterceptor" class="com.akitsuki.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor"><property name="advice" ref="beforeAdvice"/></bean><bean id="pointcutAdvisor" class="com.akitsuki.springframework.aop.aspect.AspectJExpressionPointcutAdvisor"><property name="expression" value="execution(* com.akitsuki.springframework.bean.IUserDao.*(..))"/><property name="advice" ref="methodInterceptor"/></bean></beans>

这次可不得了,一下子配置了好多的bean。首先是我们的老朋友,UserDao。紧接着是我们的处理类,也就是实现了后置处理器,要去生产代理Bean的类,我们的小英雄。再往下,是我们刚才写的 UserDaoBeforeAdvice,也就是真正AOP增强的内容。然后是方法拦截器,有了它我们才能拦截下方法,同时它也配置了一个属性advice,毕竟把方法拦截下来之后,具体做什么,还得看advice,自然就是我们上面的那个advice了。最后是我们的Advisor,它里面配置了表达式,用来表明我们具体要拦截哪个方法。这里的配置是拦截 IUserDao的所有方法。然后它也有一个Advice,是我们的方法拦截器。到这里可能会有点乱,我们理一下。

我们的Advice,有两个:一个是 UserDaoBeforeAdvice,这个是增强的内容。一个是 MethodBeforeAdviceInterceptor,这个是方法拦截器,用来拦下方法的执行。具体的依赖关系是下面这样:

AspectJExpressionPointcutAdvisor通过表达式进行匹配,依赖 MethodBeforeAdviceInterceptor进行方法的拦截。MethodBeforeAdviceInterceptor完成拦截后,则依赖 UserDaoBeforeAdvice来执行增强内容。而这一系列过程,则都是通过 DefaultAdvisorAutoProxyCreator在前置处理器中进行组装完成的。这就是整体的结构。

最后是我们的主要测试类

package com.akitsuki.springframework;import com.akitsuki.springframework.bean.IUserDao;
import com.akitsuki.springframework.context.support.ClasspathXmlApplicationContext;
import org.junit.Test;/*** @author ziling.wang@hand-china.com* @date 2022/12/5 16:46*/
public class ApiTest {@Testpublic void test() {ClasspathXmlApplicationContext context = new ClasspathXmlApplicationContext("classpath:spring.xml");IUserDao userDao = context.getBean("userDao", IUserDao.class);System.out.println(userDao.queryUserName(1L));}
}

嗯,非常简单,从这里完全看不出AOP的影子

测试结果

方法执行了:public abstract java.lang.String com.akitsuki.springframework.bean.IUserDao.queryUserName(java.lang.Long)
akitsukiProcess finished with exit code 0

结合的非常好~方法名被成功打印出来了。而且在使用中完全感觉不到AOP的存在,真正的润如细无声,无侵入性。

相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring,这里对应的代码是mini-spring-12

手写Spring-第十二章-Engage!将AOP和Spring生命周期结合起来相关推荐

  1. 价格奥秘-在超市遇见亚当斯密--第十二章 生活与价格有关,生命不靠金钱决定

    第十二章生活与价格有关,生命不靠金钱决定 在毕业典礼这一天,鲁思醒得很早.她查了一下电子邮件,看了助理前一天深夜发来的活动日程.要和鲍勃.巴克曼及艺术与科学系的系主任一起共进早餐.她不知道该怎么办.就 ...

  2. 程序员编程艺术第一 二十二章集锦与总结(教你如何编程)

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 程序员编 ...

  3. 程序员编程艺术第一~二十二章集锦与总结(教你如何编程)

    程序员编程艺术第一~二十二章集锦与总结(教你如何编程) 作者:July.编程艺术室. 出处:http://blog.csdn.net/v_JULY_v . 题记 好久没更新博客了,虽只有一个月,但对我 ...

  4. 《深入理解 Spring Cloud 与微服务构建》第十二章 服务注册和发现 Consul

    <深入理解 Spring Cloud 与微服务构建>第十二章 服务注册和发现 Consul 文章目录 <深入理解 Spring Cloud 与微服务构建>第十二章 服务注册和发 ...

  5. Shiro第十二章-与Spring集成、配置文件初解

    简介 Shiro的组件都是Javabean/pojo式的组件,所以非常容易使用Spring进行组件管理,可以非常方便得从ini配置转为Spring配置(如xml配置文件). JavaSE 依赖 < ...

  6. 「第十二章」Web框架安全

    「第三篇」服务器端应用安全批注[--] 表示他人.自己.网络批注参考资料来源于* 书中批注* CSDN* GitHub* Google* 维基百科* YouTube* MDN Web Docs由于编写 ...

  7. 追风筝的人 第十二章

    第十二章 在阿富汗,雅尔达是回历中嘉帝月的第一夜,也是冬天的第一夜,一年之中最长的夜晚.按照风俗,哈桑和我会熬到深夜,我们把脚藏在火炉桌下面,阿里将苹果皮丢进炉子,给我们讲苏丹和小偷的古老传说,度过漫 ...

  8. 韦东山 IMX6ULL和正点原子_「正点原子Linux连载」第十二章官方SDK移植试验

    1)实验平台:正点原子Linux开发板 2)摘自<正点原子I.MX6U嵌入式Linux驱动开发指南> 关注官方微信号公众号,获取更多资料:正点原子 第十二章官方SDK移植试验 在上一章中, ...

  9. 李弘毅机器学习笔记:第十二章—Recipe of Deep Learning

    李弘毅机器学习笔记:第十二章-Recipe of Deep Learning 神经网络的表现 如何改进神经网络? 新的激活函数 梯度消失 怎么样去解决梯度消失? Adaptive Learning R ...

最新文章

  1. 30分钟正则表达式入门。
  2. iOS 绘制圆角矩形
  3. Define and Publish Platform Events
  4. buildPyramid函数
  5. unet作为服务器不显示画面,UNet:无法生成服务器对象(不使用NetworkManager)
  6. python3 xlsxwiter模块插入matplotlib保存到io.BytesIO中的图
  7. 你手中的iPhone 7已过时!被苹果列为清仓产品,或为iPhone SE2让路
  8. 戴尔携手EMC战略合作续签至2013年
  9. [置顶]       jQuery旋转插件—rotate
  10. 学MySQL,这篇万字总结,真的够用了
  11. 【脑洞探究】等公交该站在哪儿比较合适?——关于减少吸入空气污染物(pm2.5 or 雾霾等)而选择合适等候公交车位置的探究
  12. Aqua Data Studio 执行HiveSql的问题
  13. 微信emoji表情包存入数据库报错处理
  14. DirectX 环境搭建问题汇总
  15. 文献综述_软件单元测试
  16. 线性回归中常见的一些统计学术语(RSE RSS TSS ESS MSE RMSE R2 Pearson's r)
  17. 华为nova2自带计算机,华为Nova2 root图文教程 华为Nova2获取root权限的方法
  18. Shell脚本对ps命令隐藏参数
  19. MD 风格的 Dialog ---- MaterialDialog
  20. China Quarantines Mexicans

热门文章

  1. 【Python】List内元素两两组合(适用于txt文件数据转坐标形式)
  2. libreoffice安装教程_【Linux】Centos7安装LibreOffice
  3. 【Pytorch深度学习实战】(12)神经风格迁移(Neural Style Transfer)
  4. postgres启动服务器时missing or erroneous pg_hba.conf file
  5. C# 微信公众号导出历史文章
  6. 把腾讯搬上云:云服务器 CVM 的半部进化史
  7. StatefulSet如何提供稳定的网络标识和状态
  8. sakai配置(中文版)(四)
  9. 蓝牙基带调制(GFSK)
  10. 工业激光打印机的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告