前言
在开始分析AOP之前,大家可以参考我的前2篇博客。
Java动态代理(https://www.jianshu.com/p/e6914f48f2a9)
Spring IOC(https://www.jianshu.com/p/3c70f548481d)
AOP即面向切面编程,其本质上就是把大多数接口都要做或者必须做的事情委托给代理类来做。比如所有人都要买房子,那么大家可以把这件事交给房产交易平台。网上博客翻了个底朝天也没有找到哪个博客把AOP源码解析部分讲的比较透彻的,大多数都让人摸不着头脑,即便给了aop入口说的也不是很清晰。本篇博客将以赵本山和宋丹丹的经典小品钟点工为例,讲讲把大象装冰箱的故事,来详细的描述下面向切面编程。
所用代码地址:https://github.com/KouLouYiMaSi/SpringDecode
一、Spring配置面向切面编程
1、ApplicationContext.xml配置
除了两个bean:zhaobenshan和adviceElephantTools,其他bean可以忽略。Aop配置中配置了切面myaop(切面想象成大象装冰箱服务公司),待切入的切入点logPointcut(这里使用了aspectj表达式,看看就行),切入的两个方法before和after。
值得注意的是本xml头部信息里需要有aop的相关声明!
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean id="springHelloWorld"
class="com.yiibai.tutorial.spring.helloworld.impl.SpringHelloWorld"></bean>
<bean id="strutsHelloWorld"
class="com.yiibai.tutorial.spring.helloworld.impl.StrutsHelloWorld"></bean>
<bean id="helloWorldService" class="com.yiibai.tutorial.spring.helloworld.HelloWorldService">
<property name="helloWorld" ref="strutsHelloWorld" />
</bean>
<bean id="zhaobenshan" class="com.yiibai.tutorial.spring.aop.Zhaobenshan" />
<bean id="adviceElephantTools" class="com.yiibai.tutorial.spring.aop.AdviceElephantTools" />
<aop:config>
<aop:aspect id="myaop" ref="adviceElephantTools"> <!--切面名称-->
<aop:pointcut id="logPointcut"
expression="execution(* com.yiibai.tutorial.spring.aop.*.*(..))" /> <!--切入点-->
<aop:before method="before" pointcut-ref="logPointcut" /> <!--前置方法-->
<aop:after method="after" pointcut-ref="logPointcut" /> <!--后置方法-->
</aop:aspect>
</aop:config>
</beans>
2、两个bean配置
这里为了使用JDK动态代理而设计了一个接口,spring aop会默认使用jdk动态代理,在没有接口的情况下会使用cglib动态代理。
赵本山需要实现大象工具接口来把大象装冰箱里,但是大象工具切面类还提供了打开冰箱门和关闭冰箱门等操作。如果世界上有1000个赵本山那么只需要一个大象切面类就能为所有赵本山提供打开冰箱门和关闭冰箱门操作。
public interface PuttingTool {
public abstract void putElephantToRefrigerator();
}
public class Zhaobenshan implements PuttingTool {
public void putElephantToRefrigerator() {
System.out.println("把大象装进冰箱里!");
}
}
public class AdviceElephantTools {
public void before() {
System.out.println("打开冰箱门。。。");
}
public void after() {
System.out.println("关上冰箱门。。。");
}
public void on() {
System.out.println("把大象塞进冰箱");
}
}
3、测试类和打印结果
由于我们上面配置了aopbefore和aopafter,所以在切点方法前后执行了打开冰箱门和关闭冰箱门操作,这里的赵本山本质上不是赵本山,而是赵本山的代理类。所以打印isInstance会是false。
public class HelloProgram {
public static void main(String[] args) {
@SuppressWarnings("resource")
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
PuttingTool zhaoBenShan = (PuttingTool) context.getBean("zhaobenshan");
System.out.println(zhaoBenShan.getClass().isInstance(Zhaobenshan.class));
zhaoBenShan.putElephantToRefrigerator();
}
}
打印结果:
false
打开冰箱门。。。
把大象装进冰箱里!
关上冰箱门。。。
二、源码追踪
由于我们没有设置bean的懒加载,所以在初始化容器的时候就已经把bean生成好了,在追踪源码之前希望你有几样东西,Eclipse+maven(下载源代码勾选上)+ctrl按键
step1
ctrl+鼠标左键单击,按照调用链进入方法
ClassPathXmlApplicationContext-->ClassPathXmlApplicationContext.this(new String[] {configLocation}, true, null);-->ClassPathXmlApplicationContext.refresh()。这个refresh方法我们比较熟悉,但对于aop来说我们只关注obtainFreshBeanFactory和registerBeanPostProcessors以及finishRefresh方法。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
-----省略异常-----
}
step2
来看obtainFreshBeanFactory方法调用链
ctrl+鼠标左键单击 AbstractApplicationContext.refreshBeanFactory-->AbstractRefreshableApplicationContext.refreshBeanFactory-->AbstractXmlApplicationContext.loadBeanDefinitions-->AbstractBeanDefinitionReader.loadBeanDefinitions-->XmlBeanDefinationReader.loadBeanDefinitions-->XmlBeanDefinationReader.doLoadBeanDefinitions-->XmlBeanDefinationReader.registerBeanDefinitions-->DefaultBeanDefinitionDocumentReader.registerBeanDefinitions-->DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions-->DefaultBeanDefinitionDocumentReader.parseBeanDefinitions
这个parseBeanDefinitions方法就把xml中的所有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);
}
}
继续BeanDefinitionParserDelegate.parseCustomElement方法,这里的namespaceUri就是我们前面提到过的那个xml头http://www.springframework.org/schema/aop。从这个namespaceUri中解析出来一个处理器NamespaceHandler,该处理器持有
{aspectj-autoproxy=org.springframework.aop.config.AspectJAutoProxyBeanDefinitionParser@149e0f5d, spring-configured=org.springframework.aop.config.SpringConfiguredBeanDefinitionParser@1b1473ab, config=org.springframework.aop.config.ConfigBeanDefinitionParser@2f7c2f4f}
这三个parser用来处理定制的bean。
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.parse最终调用的是ConfigBeanDefinationParser.parse这个方法,这个方法就一目了然了,这里对xml中aop:config标签进行了分析,具体的分析逻辑这里就不详细介绍了
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;
}
上面这些方法全都执行完之后,即loadBeanDefinition方法执行完之后beanFactory就已经有构建zhaobenshan,
adviceElephantTools,
org.springframework.aop.config.internalAutoProxyCreator,
org.springframework.aop.aspectj.AspectJPointcutAdvisor#0,
org.springframework.aop.aspectj.AspectJPointcutAdvisor#1,
logPointcut
这些bean的能力了。
step3
回到refresh方法,查看registerBeanPostProcessors调用链
AbstractApplicationContext.registerBeanPostProcessors-->PostProcessorRegistrationDelegate.registerBeanPostProcessors。这个方法实际上是在beanFactory即IOC容器中注册了PostProcessors,这些被注册的Processors会在bean创建完之后调用,你可能会问这个processor是啥,实际上由于前面BeanFactory已经有了proxy相关的bean定义,所以这个processor就是用来创建代理类的。
step4
回到refresh方法,沿着调用链finishBeanFactoryInitialization-->DefaultListableBeanFactory.preInstantiateSingletons,这个方法会提前加载一些bean进入内存,即那些没有设置为懒加载的bean都会通过该类被放到内存中。
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Pre-instantiating singletons in " + this);
}
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
当创建到zhaobenshan的bean的时候,执行了AbstractBeanFactory.getBean-->AbstractBeanFactory.doGetBean-->AbstractAutowireCapableBeanFactory.doGetBean-->AbstractAutowireCapableBeanFactory.createBean-->AbstractAutowireCapableBeanFactory.doCreateBean-->AbstractAutowireCapableBeanFactory.initializeBean-->AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization-->AbstractAutoProxyCreator.postProcessAfterInitialization
就是下面这个方法,执行了代理类的创建。
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@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;
}
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.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
protected Object createProxy(
Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
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());
}
OK,经过上面的过程我们可以看到,最终底层调用了JDK或者cglib代理创建代理类。这样实例化出来的类就是代理类了,结合前两篇博客就可以把aop过程吃透了。
总结
如果你有兴趣自己研究源码可以使用debug断点的模式,我是F5一个一个往里面进的,这个过程虽然麻烦,但是确实管用。
代理类的两个重要方法实际上是在:registerBeanPostProcessors + applyBeanPostProcessorsAfterInitialization(beanProcessor.postProcessAfterInitialization(result, beanName))这两个方法上,前者用于注册AOP创建代理Processor,后者用于调用Processor生成代理类!