spring aop和动态代理

来源:互联网 发布:淘宝店铺号怎么看 编辑:程序博客网 时间:2024/06/05 11:52

首先简单介绍一下动态代理的概念。

书面定义:为其他对象提供一种代理以控制对这个对象的访问。

通俗的来说,想象一下这样一个场景。我想要去旅行,需要一张机票。但是这个季节的机票很难买,而且我不知道向谁买,如何选择等等。此时我通常就会去找代理商,这个代理商一定程度上代表了我,因为我提供给他了我的基本信息,他会按照我的信息去帮我购买机票(亦或是那些卖机票的人会来找我的代理商,我的代理商会把我的基本信息告诉那些人,并通知我:有个卖机票的人来找我了)<—这种说法与动态代理的方式比较相似

在java中,一般使用代理类有个好处,即被代理的对象不会被完全暴露出来,代理类会负责按照规则控制或者过滤对象的访问请求。

java动态代理使用反射来实现功能。参考一下jdk1.6中java.lang.reflect.Proxy的源码。

Proxy类中的主要方法:

/**     * 用于获取指定的代理类,多用于防止同一类进行重复的动态     * @param loader 被代理对象所指定的类加载器     * @param interfaces 基于接口的类对象     * @return     */    public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)/**     * 用于生成动态代理类实例     * @param loader 被代理对象所指定的类加载器     * @param interfaces 基于接口的类对象     * @param h 动态代理的调用处理器(控制代理访问的关键)     * @return     */    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)/**     * 获得指定代理类的调用处理器     *     * @param proxy 指定代理类     * @return     */    public static InvocationHandler getInvocationHandler(Object proxy)
InvocationHandler调用处理器内的主要方法:

/**     * 调用处理器的主要方法,用于预处理或发送消息到被代理类实例执行相关操作     *     * @param proxy  代理类对象     * @param method 被调用的方法对象(通过反射获取)     * @param args   被调用的方法对应的参数     * @return     * @throws Throwable     */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
其中生成代理对象的newProxyInstance方法中的第二个参数 Class<?>[] interfaces 是被代理对象所实现的接口,所以jdk的动态代理又称为基于接口的动态代理。代理对象必须是接口类对象。

invoke方法中,你可以通过method.invoke(java反射机制)来调用被代理对象的真实方法,在调用之前或之后就可以插入控制语句。

实际上,通过上面的了解,你已经可以通过proxy实现一个简单的AOP。需要注意一点的是,由于被代理对象是接口类对象,因此你需要在调用指定类加载器(ClassLoader)时,指定接口具体实现类的类加载器。
如果你有使用spring的话,那就直接将实现注入即可。 

通过上面的了解,相信你也知道了spring aop的机制正是基于动态代理和反射,CGlib全称Code Generation Library是一个动态代码生成库,底层基于asm框架,可以动态生成class等字节文件。其原理是,动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快,因此它可以在运行期扩展Java类与实现Java接口
利用这一点,CGlib可以为没有实现接口的类提供代理,对于上面的jdk动态代理,是一个很好的补充。不过由于final关键字无法被继承的原因,它无法包装final方法。
  
spring aop的动态代理分为两类,一类是基于接口的动态代理,即java proxy,另一类是基于CGlib的类动态代理,你需要根据你的代理对象的类型手动指定代理模式,spring会根据你的设置进行响应。
你可以通过设置<aops:aspectj-autoproxy proxy-target-class="true">或者@EnableAspectJAutoProxy 来启用cglib代理,否则你将无法对类对象创建切面。

下面是一个使用动态代理的实例:
首先,定义一个接口,并声明了两个方法:

{    public void rent();        public void hello(String str);}

接着,定义一个该接口的实现,即我们真实的被代理对象

public class RealSubject implements Subject{    @Override    public void rent()    {        System.out.println("I want to rent my house");    }        @Override    public void hello(String str)    {        System.out.println("hello: " + str);    }}
下一步,定义动态代理类,前面说过所有的动态代理类都需要实现InvocationHandler接口,

public class DynamicProxy implements InvocationHandler{    // 这个就是我们要代理的真实对象    private Object subject;        //    构造方法,给我们要代理的真实对象赋初值    public DynamicProxy(Object subject)    {        this.subject = subject;    }        @Override    public Object invoke(Object object, Method method, Object[] args)            throws Throwable    {        //  在代理真实对象前我们可以添加一些自己的操作        System.out.println("before rent house");                System.out.println("Method:" + method);                //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用,这里的object是代理对象而非真实对象        Object result = method.invoke(subject, args);                //  在代理真实对象后我们也可以添加一些自己的操作        System.out.println("after rent house");                return result;    }}

最后是调用的实例代码:

public class Client{    public static void main(String[] args)    {        //    我们要代理的真实对象        Subject realSubject = new RealSubject();        //    我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的        InvocationHandler handler = new DynamicProxy(realSubject);        /*         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上         */        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject                .getClass().getInterfaces(), handler);                System.out.println(subject.getClass().getName());        subject.rent();        subject.hello("world");    }}

注意,这里的subject对象是代理对象,而realSubject对象是被代理对象,subject对象是由我们的InvocationHandler实现类生成的,因此其内部方法被隐藏,且在调用前后封装了其他控制操作。
而在InvocationHandler的newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

下面是输出打印的信息:

$Proxy0before rent houseMethod:public abstract void com.xiaoluo.dynamicproxy.Subject.rent()I want to rent my houseafter rent housebefore rent houseMethod:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)hello: worldafter rent house

同时这里输出的$Proxy0表示,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
使用了动态代理之后,即使接口类改变,我们的代理类也不用相应变化,实现了两者的解耦,所有相关接口的实现都可以复用。

 
原创粉丝点击