mini-spring-course

17|动态代理:如何在运行时插入逻辑?

你好,我是郭屹。今天我们继续手写MiniSpring。

从这节课开始,我们就要进入AOP环节了。在学习之前,我们先来了解一下是AOP怎么回事。

AOP,就是面向切面编程(Aspect Orient Programming),这是一种思想,也是对OOP面向对象编程的一种补充。你可能会想:既然已经存在OOP面向对象编程了,为什么还需要AOP面向切面编程呢?

这是因为在许多场景下,一个类的方法中,除了业务逻辑,通常还会包括其他比较重要但又不算主业务逻辑的例行性逻辑代码,比如常见的日志功能,它不影响我们的主业务逻辑,但又能在必要时定位问题,几乎每一个业务方法中都需要。又比如权限检查、事务处理,还有性能监控等等,都是这种情况。

显而易见,日志这类例行性逻辑,在任何一个业务方法实现中都是需要的。如果简单地将这些代码写在业务方法中,会出现两个后果,第一,我们就会将日志之类的代码重复地编写多次;第二,一个业务方法中会包含很多行例行代码,去看源代码会发现方法中多数语句不是在做业务处理。

有专业进取心的程序员就会思考一个问题, 有没有办法将这些例行性逻辑单独抽取出来,然后在程序运行的时候动态插入到业务逻辑中呢? 正是因为这个疑问,AOP应运而生了。这个问题听起来似乎无解,程序在运行时改变程序本身,似乎有点不可思议。我们研究一下Java,就会惊奇地发现,Java里面早就给我们提供了一个手段: 动态代理。我们可以利用它来开展我们的工作。

代理模式

我们一步步来,先从代理讲起。

图片

看图,我们知道真正干活儿的类是RealSubject,具体则是由DoAction()执行任务。Proxy作为代理提供一个同样的DoAction(),然后调用RealSubject的DoAction()。它们都实现Subject接口,而Client应用程序操作的是Subject 接口。

简单说来,就是在Client应用程序与真正的服务程序RealSubject之间增加了一个Proxy。

我们举例说明,先定义一个服务类接口。

public interface Subject {
	String doAction(String name);
}

再定义具体的服务类。

public class RealSubject implements Subject {
	public String doAction(String name) {
		System.out.println("real subject do action "+name);
		return "SUCCESS";
	}
}

最后再定义一个代理类。

public class ProxySubject implements Subject {
	Subject realsubject;
	public ProxySubject() {
		this.realsubject = new RealSubject();
	}
	public String doAction(String name) {
		System.out.println("proxy control");
		String rtnValue = realsubject.doAction(name);
		return "SUCCESS";
	}
}

通过代码我们看到,代理类内部包含了一个真正的服务类,而代理类给外部程序提供了和真正的服务类同样的接口。当外部应用程序调用代理类的方法时,代理类内部实际上会转头调用真正的服务类的相应方法,然后把真正的服务类的返回值直接返回给外部程序。这样做就隐藏了真正的服务类的实现细节。

同时,在调用真正的服务方法之前和之后,我们还可以在代理类里面做一点手脚,加上额外的逻辑,比如上面程序中的 System.out.println("proxy control");,这些额外的代码,大部分都是一些例行性的逻辑,如权限和日志等。

最后我们提供一个客户程序使用这个代理类。

public class Client {
	public static void main(String[] args) {
		Subject subject = new ProxySubject();
		subject.doAction("Test");
	}
}

总结一下,代理模式能够让我们在业务处理之外添加例行性逻辑。但是这个经典的模式在我们这里不能直接照搬,因为这个代理是静态的,要事先准备好。而我们需要的是在相关的业务逻辑执行的时候,动态插入例行性逻辑,不需要事先手工静态地准备这些代理类。解决方案就是 Java中的动态代理技术。

动态代理

Java提供的动态代理可以对接口进行代理,在代理的过程中主要做三件事。

  1. 实现InvocationHandler接口,重写接口内部唯一的方法invoke。
  2. 使用Proxy类,通过newProxyInstance,初始化一个代理对象。
  3. 通过代理对象,代理其他类,对该类进行增强处理。

这里我们还是举例说明。首先定义一个IAction接口。

package com.test.service;
public interface IAction {
   void doAction();
}

提供一个具体实现类。

package com.test.service;
public class Action1 implements IAction {
   @Override
   public void doAction() {
      System.out.println("really do action");
   }
}

我们定义了一个DynamicProxy类,用来充当代理对象的类。

package com.test.service;
public class DynamicProxy {
   private Object subject = null;

   public DynamicProxy(Object subject) {
         this.subject = subject;
   }

   public Object getProxy() {
      return Proxy.newProxyInstance(DynamicProxy.class
            .getClassLoader(), subject.getClass().getInterfaces(),
            new InvocationHandler() {
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("doAction")) {
                System.out.println("before call real object........");
                return method.invoke(subject, args);
            }
            return null;
         }
      });
   }
}

通过这个类的实现代码可以看出,我们使用了Proxy类,调用newProxyInstance方法构建IAction接口的代理对象,而且重写了InvocationHandler接口中的invoke方法。在重写的方法中我们判断方法名称是否与接口中的doAction方法保持一致,随后加上例行性逻辑(print语句),最后通过反射调用接口IAction中的doAction方法。

通过这个操作,例行性逻辑就在业务程序运行的时候,动态地添加上去了。

我们编写一个简单的测试程序,就能直观感受到代理的效果了。

package com.test.controller;
public class HelloWorldBean {
    @Autowired
    IAction action;

    @RequestMapping("/testaop")
    public void doTestAop(HttpServletRequest request, HttpServletResponse response) {
     DynamicProxy proxy = new DynamicProxy(action);
     IAction p = (IAction)proxy.getProxy();
     p.doAction();

     String str = "test aop, hello world!";
     try {
        response.getWriter().write(str);
     } catch (IOException e) {
        e.printStackTrace();
     }
  }
}

运行这个程序,返回内容是“test aop,hello world!”。这个时候查看命令行里的内容,你就会发现还有两行输出。

before call real object........
really do action

第一行是代理对象中的输出,第二行是Action1中doAction方法的实现。

根据这个输出顺序我们发现,这个代理对象达到了代理的效果,在调用IAction的具体实现类之前进行了额外的操作,从而增强了代理类。而这个代理是我们动态增加的,而不是事先静态地手工编写一个代理类。

但是读代码,这种方式显然是不美观的,需要在业务逻辑程序中写上,对代码的侵入性太强了。

DynamicProxy proxy = new DynamicProxy(action);
IAction p = (IAction)proxy.getProxy();

这个写法跟我们手工写一个代理类实际上相差不多。这种侵入式的做法不是我们推崇的,所以我们要继续前进。

引入FactoryBean

我们的目标是 非侵入式编程, 也就是应用程序在编程的时候,它不应该手工去创建一个代理,而是使用本来的业务接口,真正的实现类配置在外部,代理类也是配置在外部。

@Autowired
IAction action;

@RequestMapping("/testaop")
public void doTestAop(HttpServletRequest request, HttpServletResponse response) {
    action.doAction();
}

配置如下:

<bean id="realaction" class="com.test.service.Action1" />
<bean id="action" class="com.minis.aop.ProxyFactoryBean" >
    <property type="java.lang.Object" name="target" ref="realaction"/>
</bean>

业务类中自动注入的是一个action,也就是上面代码里的ProxyFactoryBean类,这个类内部包含了真正干活儿的类realaction。

这里就有一个初看起来非常奇怪的需求:注册的action bean是ProxyFactoryBean类,而业务程序使用getBean(“action”)的时候,期待返回的又不是这个Bean本身,而是内部那个target。因为只有这样才能让业务程序实际调用target中的方法,外面的这个ProxyFactoryBean对我们来讲是一个入口,而不是目标。这也就要求,当业务程序使用getBean(“action”)方法的时候,这个ProxyFactoryBean应该在内部进行进一步地处理,根据target再动态生成一个代理返回,达到侵入式编程中下面这两句话的效果。

DynamicProxy proxy = new DynamicProxy(action);
IAction p = (IAction)proxy.getProxy();

上面的方案,看起来奇怪,但是确实能解决动态代理的问题。

好,现在我们就按照这个思路动手去实现。首先我们参考Spring框架,定义FactoryBean接口。

相关代码参考:

package com.minis.beans.factory;
public interface FactoryBean<T> {
    T getObject() throws Exception;
    Class<?> getObjectType();
    default boolean isSingleton() {
        return true;
    }
}

主要的方法就是getObject(),从Factory Bean中获取内部包含的对象。

接着定义FactoryBeanRegistrySupport,提供一部分通用的方法。

package com.minis.beans.factory.support;
public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry{
    protected Class<?> getTypeForFactoryBean(final FactoryBean<?> factoryBean) {
        return factoryBean.getObjectType();
    }
    protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName) {
        Object object = doGetObjectFromFactoryBean(factory, beanName);
        try {
            object = postProcessObjectFromFactoryBean(object, beanName);
        } catch (BeansException e) {
            e.printStackTrace();
        }
        return object;
    }
    //从factory bean中获取内部包含的对象
    private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) {
        Object object = null;
        try {
            object = factory.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }
}

最重要的是这个方法:doGetObjectFromFactoryBean(),从一个Factory Bean里面获取内部包含的那个target对象。

因为FactoryBeanRegistrySupport继承了DefaultSingletonBeanRegistry,所以我们接下来可以改写AbstractBeanFactory,由原本继承DefaultSingletonBeanRegistry改成继承FactoryBeanRegistrySupport,保留原有功能的同时增加了功能扩展。

我们重点要修改核心的getBean()方法。

package com.minis.beans.factory.support;
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory,BeanDefinitionRegistry{
   public Object getBean(String beanName) throws BeansException{
      Object singleton = this.getSingleton(beanName);
      if (singleton == null) {
         singleton = this.earlySingletonObjects.get(beanName);
         if (singleton == null) {
            System.out.println("get bean null -------------- " + beanName);
            BeanDefinition bd = beanDefinitionMap.get(beanName);
            if (bd != null) {
               singleton=createBean(bd);
               this.registerBean(beanName, singleton);
               //beanpostprocessor
               //step 1 : postProcessBeforeInitialization
               applyBeanPostProcessorsBeforeInitialization(singleton, beanName);
               //step 2 : init-method
               if (bd.getInitMethodName() != null && !bd.getInitMethodName().equals("")) {
                  invokeInitMethod(bd, singleton);
               }
               //step 3 : postProcessAfterInitialization
               applyBeanPostProcessorsAfterInitialization(singleton, beanName);
            }
            else {
               return null;
            }
         }
      }
      else {
      }
      //处理factorybean
      if (singleton instanceof FactoryBean) {
         return this.getObjectForBeanInstance(singleton, beanName);
      }
      else {
      }
      return singleton;
   }

我们看到在getBean()这一核心方法中,原有的逻辑处理完毕后,我们新增下面这一段。

//process Factory Bean
if (singleton instanceof FactoryBean) {
   return this.getObjectForBeanInstance(singleton, beanName);
}

根据代码实现可以看出,这里增加了一个判断,如果Bean对象是FactoryBean类型时,则调用getObjectForBeanInstance方法。

protected Object getObjectForBeanInstance(Object beanInstance, String beanName) {
    // Now we have the bean instance, which may be a normal bean or a FactoryBean.
    if (!(beanInstance instanceof FactoryBean)) {
         return beanInstance;
    }
    Object object = null;
    FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
    object = getObjectFromFactoryBean(factory, beanName);
    return object;
}

代码显示,getObjectForBeanInstance又会调用doGetObjectFromFactoryBean方法。

private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) {
    Object object = null;
    try {
        object = factory.getObject();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return object;
}

最后落实到了factory.getObject()里。由此可以看出,我们通过AbstractBeanFactory获取Bean的时候,对FactoryBean进行了特殊处理,获取到的已经不是FactoryBean本身了,而是它内部包含的那一个对象。而这个对象,也不是真正底层对应的Bean。它仍然只是一个代理的对象,我们继续往下看。

我们这个getObject()只是FactoryBean里的一个接口,接下来我们提供一下它的接口实现——ProxyFactoryBean。

package com.minis.aop;
public class ProxyFactoryBean implements FactoryBean<Object> {
    private AopProxyFactory aopProxyFactory;
    private String[] interceptorNames;
    private String targetName;
    private Object target;
    private ClassLoader proxyClassLoader = ClassUtils.getDefaultClassLoader();
    private Object singletonInstance;
    public ProxyFactoryBean() {
        this.aopProxyFactory = new DefaultAopProxyFactory();
    }
    public void setAopProxyFactory(AopProxyFactory aopProxyFactory) {
        this.aopProxyFactory = aopProxyFactory;
    }
    public AopProxyFactory getAopProxyFactory() {
        return this.aopProxyFactory;
    }
    protected AopProxy createAopProxy() {
        return getAopProxyFactory().createAopProxy(target);
    }
    public void setInterceptorNames(String... interceptorNames) {
        this.interceptorNames = interceptorNames;
    }
    public void setTargetName(String targetName) {
        this.targetName = targetName;
    }
    public Object getTarget() {
        return target;
    }
    public void setTarget(Object target) {
        this.target = target;
    }
    @Override
    public Object getObject() throws Exception {//获取内部对象
        return getSingletonInstance();
    }
    private synchronized Object getSingletonInstance() {//获取代理
        if (this.singletonInstance == null) {
            this.singletonInstance = getProxy(createAopProxy());
        }
        return this.singletonInstance;
    }
    protected Object getProxy(AopProxy aopProxy) {//生成代理对象
        return aopProxy.getProxy();
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

这段代码的核心在于,ProxyFactoryBean在getObject()方法中生成了一个代理getProxy(createAopProxy()),同样也是通过这种方式,拿到了要代理的目标对象。这里的工作就是 创建动态代理

基于JDK的实现

Spring作为一个雄心勃勃的框架,自然不会把自己局限于JDK提供的动态代理一个技术上,所以,它再次进行了包装,提供了AopProxy的概念,JDK只是其中的一种实现。

package com.minis.aop;
public interface AopProxy {
    Object getProxy();
}

还定义了factory。

package com.minis.aop;
public interface AopProxyFactory {
    AopProxy createAopProxy(Object target);
}

然后给出了基于JDK的实现。

package com.minis.aop;
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    Object target;
    public JdkDynamicAopProxy(Object target) {
        this.target = target;
    }
    @Override
    public Object getProxy() {
        Object obj = Proxy.newProxyInstance(JdkDynamicAopProxy.class.getClassLoader(), target.getClass().getInterfaces(), this);
        return obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("doAction")) {
            System.out.println("-----before call real object, dynamic proxy........");
            return method.invoke(target, args);
        }
        return null;
    }
}

package com.minis.aop;
public class DefaultAopProxyFactory implements AopProxyFactory{
    @Override
    public AopProxy createAopProxy(Object target) {
		return new JdkDynamicAopProxy(target);
    }
}

在这个实现里,我们终于看到了我们曾经熟悉的Proxy.newProxyInstance()和invoke()。利用Java的动态代理技术代理了目标对象,而这也是ProxyFactoryBean里真正要返回的Object。

这就是Spring AOP的实现原理。

测试

有了上面的工具,我们的测试程序就不需要再手动构建代理对象了,而是交给框架本身处理。而注入的对象,则通过配置文件注入属性值。

applicationContext.xml配置中新增一段内容。

<bean id="realaction" class="com.test.service.Action1" />
<bean id="action" class="com.minis.aop.ProxyFactoryBean" >
    <property type="java.lang.Object" name="target" ref="realaction"/>
</bean>

通过配置,我们在HelloWorldBean里注入的IAction对象就纳入了容器管理之中,因此后续测试的时候,直接使用action.doAction(),就能实现手动初始化JDK代理对象的效果。

package com.test.controller;
public class HelloWorldBean {
   @Autowired
   IAction action;

   @RequestMapping("/testaop")
   public void doTestAop(HttpServletRequest request, HttpServletResponse response) {
      action.doAction();

      String str = "test aop, hello world!";
      try {
         response.getWriter().write(str);
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

我们终于看到了动态代理的结果。

小结

这节课我们 利用JDK动态代理技术实现了AOP这个概念

我们介绍了代理模式实现的静态代理,然后使用了JDK的动态代理技术。在使用动态代理技术的程序代码中,我们发现它是侵入式的,不理想,所以我们就想办法把代理配置在XML文件里了。但是如果按照原有的Bean的定义,这个配置在外部文件里的代理Bean本身不能代理业务类,我们真正需要的是通过这个代理Bean来创建一个动态代理,于是引入了FactoryBean的概念,不是直接获取这个Bean本身,而是通过里面的getObject()获取到Factory Bean里面包含的对象。

这样将IoC容器里的Bean分成了两类:一是普通的Bean,二是Factory Bean。在getObject()的实现中,我们使用JDK的动态代理技术创建了一个代理。这样就实现了AOP。

另外,Spring中的代理支持JDK代理与Cglib代理两种,目前MiniSpring定义的DefaultAopProxyFactory只支持JDK代理。另一种方式我留作思考题,你可以先想一想要怎么实现。

AOP还有别的实现方案,比如AspectJ,也比较常用,在实际工程实践中,一般采用的就是AspectJ,而不是Spring AOP,因为AspectJ更加高效,功能更强。比如,AspectJ是编译时创建的代理,性能高十倍以上,而且切入点不仅仅在方法上,而是可以在类的任何部分。所以AspectJ才是完整的AOP解决方案,Spring AOP不是成功的工业级方案。之所以保留Spring AOP,一个原因是原理简单、利于理解,另一个是Rod Johnson不忍抛弃自己的心血。

完整源代码参见 https://github.com/YaleGuo/minis

课后题

学完这节课,我也给你留一道思考题。如果MiniSpring想扩展到支持Cglib,程序应该从哪里下手改造?欢迎你在留言区与我交流讨论,也欢迎你把这节课分享给需要的朋友。我们下节课见!