若是你有幸能看到java
tiny-spring
是为了学习Spring的而开发的,能够认为是一个Spring的精简版。Spring的代码不少,层次复杂,阅读起来费劲。我尝试从使用功能的角度出发,参考Spring的实现,一步一步构建,最终完成一个精简版的Spring。 有人把程序员与画家作比较,画家有门基本功叫临摹,tiny-spring能够算是一个程序的临摹版本-从本身的需求出发,进行程序设计,同时对著名项目进行参考。git
AOP分为配置(Pointcut,Advice),织入(Weave)两部分工做,固然还有一部分是将AOP整合到整个容器的生命周期中。程序员
7.step7-使用JDK动态代理实现AOP织入github
织入(weave)相对简单,咱们先从它开始。Spring AOP的织入点是AopProxy,它包含一个方法Object getProxy()来获取代理后的对象。spring
在Spring AOP中,我以为最重要的两个角色,就是咱们熟悉的MethodInterceptor和MethodInvocation(这两个角色都是AOP联盟的标准),它们分别对应AOP中两个基本角色:Advice和Joinpoint。Advice定义了在切点指定的逻辑,而Joinpoint则表明切点express
切点通知器相关接口app
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
复制代码
Spring的AOP只支持方法级别的调用,因此其实在AopProxy里,咱们只须要将MethodInterceptor放入对象的方法调用便可。框架
使用jdk动态代理ide
public class JdkDynamicAopProxy extends AbstractAopProxy implements InvocationHandler {
public JdkDynamicAopProxy(AdvisedSupport advised ) {
super(advised);
}
@Override
public Object getProxy() {
return Proxy.newProxyInstance(getClass().getClassLoader(),advised.getTargetSource().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提取拦截的方法
MethodInterceptor methodInterceptor = (MethodInterceptor) advised.getMethodInterceptor();
//比较传入的方法和对象的方法是否一致,若是一致则调用传入的方法,
if (advised.getMethodMatcher() != null
&& advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
//这里应该是先调用拦截的方法,而后调用原始对象的方法。可是通常括号里的东西不是优先吗?括号里面好像就只有赋值操做而已。
return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),method, args));
} else {
return method.invoke(advised.getTargetSource().getTarget(), args);
}
}
复制代码
咱们称被代理对象为TargetSource,而AdvisedSupport就是保存TargetSource和MethodInterceptor的元数据对象。这一步咱们先实现一个基于JDK动态代理的JdkDynamicAopProxy,它能够对接口进行代理。因而咱们就有了基本的织入功能。学习
@Test
public void testInterceptor() throws Exception {
// --------- helloWorldService without AOP
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();
// --------- helloWorldService with AOP
// 1. 设置被代理对象(Joinpoint)
AdvisedSupport advisedSupport = new AdvisedSupport();
TargetSource targetSource = new TargetSource(helloWorldService, HelloWorldServiceImpl.class,
HelloWorldService.class);
advisedSupport.setTargetSource(targetSource);
// 2. 设置拦截器(Advice)
TimerInterceptor timerInterceptor = new TimerInterceptor();
advisedSupport.setMethodInterceptor(timerInterceptor);
// 3. 建立代理(Proxy)
JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport);
HelloWorldService helloWorldServiceProxy = (HelloWorldService) jdkDynamicAopProxy.getProxy();
// 4. 基于AOP的调用
helloWorldServiceProxy.helloWorld();
}
复制代码
git checkout step-8-invite-pointcut-and-aspectj
复制代码
完成了织入以后,咱们要考虑另一个问题:对什么类以及什么方法进行AOP?对于“在哪切”这一问题的定义,咱们又叫作“Pointcut”。Spring中关于Pointcut包含两个角色:ClassFilter
和MethodMatcher
,分别是对类和方法作匹配。Pointcut有不少种定义方法,例如类名匹配、正则匹配等,可是应用比较普遍的应该是和AspectJ
表达式的方式。
AspectJ
是一个“对Java的AOP加强”。它最先是实际上是一门语言,咱们跟写Java代码同样写它,而后静态编译以后,就有了AOP的功能。下面是一段AspectJ代码:
aspect PointObserving {
private Vector Point.observers = new Vector();
public static void addObserver(Point p, Screen s) {
p.observers.add(s);
}
public static void removeObserver(Point p, Screen s) {
p.observers.remove(s);
}
...
}
复制代码
这种方式无疑过重了,为了AOP,还要适应一种语言?因此如今使用也很少,可是它的Pointcut
表达式被Spring借鉴了过来。因而咱们实现了一个AspectJExpressionPointcut
:
@Test
public void testMethodInterceptor() throws Exception {
String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
aspectJExpressionPointcut.setExpression(expression);
boolean matches = aspectJExpressionPointcut.getMethodMatcher().matches(HelloWorldServiceImpl.class.getDeclaredMethod("helloWorld"),HelloWorldServiceImpl.class);
Assert.assertTrue(matches);
}
复制代码
public class AspectJExpressionPointcutTest {
@Test
public void testClassFilter() throws Exception {
String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
aspectJExpressionPointcut.setExpression(expression);
boolean matches = aspectJExpressionPointcut.getClassFilter().matches(HelloWorldService.class);
Assert.assertTrue(matches);
}
@Test
public void testMethodInterceptor() throws Exception {
String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
aspectJExpressionPointcut.setExpression(expression);
boolean matches = aspectJExpressionPointcut.getMethodMatcher().matches(HelloWorldServiceImpl.class.getDeclaredMethod("helloWorld"),HelloWorldServiceImpl.class);
Assert.assertTrue(matches);
}
复制代码
git checkout step-9-auto-create-aop-proxy
复制代码
万事俱备,只欠东风!如今咱们有了Pointcut和Weave技术,一个AOP已经算是完成了,可是它尚未结合到Spring中去。怎么进行结合呢?Spring给了一个巧妙的答案:使用BeanPostProcessor
。
BeanPostProcessor是BeanFactory提供的,在Bean初始化过程当中进行扩展的接口。只要你的Bean实现了BeanPostProcessor
接口,那么Spring在初始化时,会优先找到它们,而且在Bean的初始化过程当中,调用这个接口,从而实现对BeanFactory核心无侵入的扩展。
那么咱们的AOP是怎么实现的呢?咱们知道,在AOP的xml配置中,咱们会写这样一句话:
<aop:aspectj-autoproxy/>
复制代码
它其实至关于:
<bean id="autoProxyCreator" class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"></bean>
复制代码
AspectJAwareAdvisorAutoProxyCreator
就是AspectJ方式实现织入的核心。它实际上是一个BeanPostProcessor。在这里它会扫描全部Pointcut,并对bean作织入。
为了简化xml配置,我在tiny-spring中直接使用Bean的方式,而不是用aop前缀进行配置:
<bean id="autoProxyCreator" class="us.codecraft.tinyioc.aop.AspectJAwareAdvisorAutoProxyCreator"></bean>
<bean id="timeInterceptor" class="us.codecraft.tinyioc.aop.TimerInterceptor"></bean>
<bean id="aspectjAspect" class="us.codecraft.tinyioc.aop.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="timeInterceptor"></property>
<property name="expression" value="execution(* us.codecraft.tinyioc.*.*(..))"></property>
</bean>
复制代码
TimerInterceptor
实现了MethodInterceptor
(实际上Spring中还有Advice
这样一个角色,为了简单,就直接用MethodInterceptor了)。
至此,一个AOP基本完工。
git checkout step-10-invite-cglib-and-aopproxy-factory
复制代码
前面的JDK动态代理只能对接口进行代理,对于类则无能为力。这里咱们须要一些字节码操做技术。这方面大概有几种选择:ASM
,CGLib
和javassist
,后二者是对ASM
的封装。Spring中使用了CGLib。
在这一步,咱们还要定义一个工厂类ProxyFactory
,用于根据TargetSource类型自动建立代理,这样就须要在调用者代码中去进行判断。
另外咱们实现了Cglib2AopProxy
,使用方式和JdkDynamicAopProxy
是彻底相同的。
public class Cglib2AopProxy extends AbstractAopProxy {
public Cglib2AopProxy(AdvisedSupport advised) {
super(advised);
}
//经过cglib类库建立了一个代理类的实例
@Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(advised.getTargetSource().getTargetClass());
enhancer.setInterfaces(advised.getTargetSource().getInterfaces());
//设置代理类的通知方法,至关于设置拦截器方法
enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
Object enhanced = enhancer.create();
return enhanced;
}
//方法拦截器
private static class DynamicAdvisedInterceptor implements MethodInterceptor {
private AdvisedSupport advised;
private org.aopalliance.intercept.MethodInterceptor delegateMethodInterceptor;
private DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
this.delegateMethodInterceptor = advised.getMethodInterceptor();
}
//调用代理类的方法(代理类与原始类是父子关系,还有一种是兄弟关系,调用实质是调用原始类的方法)
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (advised.getMethodMatcher() == null
|| advised.getMethodMatcher().matches(method, advised.getTargetSource().getTargetClass())) {
//这里也应该是先调用拦截方法,而后调用原始对象的方法
return delegateMethodInterceptor.invoke(new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy));
}
return new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy).proceed();
}
}
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
private final MethodProxy methodProxy;
public CglibMethodInvocation(Object target, Method method, Object[] args, MethodProxy methodProxy) {
super(target, method, args);
this.methodProxy = methodProxy;
}
@Override
public Object proceed() throws Throwable {
return this.methodProxy.invoke(this.target, this.arguments);
}
}
复制代码
有一个细节是CGLib建立的代理是没有注入属性的, Spring的解决方式是:CGLib仅做代理,任何属性都保存在TargetSource中,使用MethodInterceptor=>TargetSource的方式进行调用。
至此,AOP部分完工。
一、经过AspectJ表达式处理AOP的主要类和关系
二、使用cglib动态代理
三、两种动态代理
一层一层的封装,合理运用组合、继承类或接口来赋予、加强类的相应功能
接口的运用:
模板方法模式以及hook方法的应用: 例如: 在AbstractBeanFactory中规范了bean的加载,实例化,初始化,获取的过程。AutowireCapableBeanFactory里实现了hook方法(applyPropertyValues方法),该方法在AbstractBeanFactory#initializeBean方法中调用,AbstractBeanFactory中有默认的hook方法空实现。
工厂方法模式的应用:例如:BeanFactory#getBean,由子类决定怎样去获取bean并在获取时进行相关操做。工厂方法把实例化推迟到子类。
外观(门面)模式的运用:ClassPathXmlApplicationContext对 Resouce 、 BeanFactory、BeanDefinition 进行了功能的封装,解决 根据地址获取资源经过 IoC 容器注册bean定义并实例化,初始化bean的问题,并提供简单运用他们的方法。
代理模式的运用:
单例模式的运用:
策略模式: 这里有个想法,看ClassPathXMLApplicationContext构造方法能够知道是默认用自动装配的策略,在这里能够另外本身写个类继承AbstractBeanFactory,重写applyPropertyValues方法实现装配策略,在初始化的时候就能够选择不一样的装配策略了。