JDK设计模式之—动态代理

代理模式的特点

代理模式是常用的java设计模式,它的特征是代理类与委托类有同样的接口。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类。

代理类的对象并不是真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

按照代理类的创建时期,代理类可分为两种

  静态代理类:由程序员创建源代码,在对其编译。在程序运行之前,代理类的.class文件就已经存在了。

  动态代理类:在程序运行时,通过反射机制创建而成。

静态代理

1.首先我们写一个被代理类

package javaee.net.cn.proxy;
/**
 * 需要动态代理的接口
 */
public interface Subject{
    public void save();
}

2.在写一个实现类(实际被代理的对象)

package javaee.net.cn.proxy;
/**
 * 实际对象
 */
public class RealSubject implements Subject{
    public void save(){
       System.out.println("insert into ......");
    }
    
}

3 手动编写代理类

public class StaticProxy implements Subject{
    private Subject subject;
    public StaticProxy(Subject subject){
        this.subject=subject;
    }
    @Override
    public String save() {
        System.out.println("trancation start");
        String result = subject.save();
        System.out.println("trancation commit");
        return result;
    }
}

动态代理

与静态代理对照的是动态代理,动态代理的字节码在程序运行时由java反射机制动态生成,无需程序员手工编写它的源代码。

动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为java反射机制可以生成任意类型的动态代理类。

java.lang.reflect包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。

Proxy提供了创建动态代理类及其实例的静态方法。

1)getProxyClass(ClassLoader loader,Class<?>... interfaces)

  静态方法负责创建动态代理类,参数loader指定动态代理类的类加载器,参数interfaces指定动态代理类需要实现的所有接口。

2)newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 

静态方法负责创建代理类的实列,参数loader指定动态代理类的类加载器,参数interfaces指定动态代理类需要实现的所有接口。

以下两种方式都创建了实现Subject接口的动态代理类的实列。

    /**方式一*/
    //创建InvocationHandler对象
    InvocationHandler invocationHandler = new InvocationHandler(...);
    //创建动态代理类
    Class proxyClass = Proxy.getProxyClass(Subject.class.getClassLoader(), new Class[] {Subject.class});
    //创建动态代理类的实列
    Subject subject = (Subject) proxyClass.getConstructor(new Class[] {InvocationHandler.class}).newInstance(new Object[] {invocationHandler});
    
    /**方式二*/
    //创建InvocationHandler对象
    InvocationHandler invocationHandler = new InvocationHandler(...);
    //直接创建动态代理类的实列
    Subject subject = Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[] {Subject.class},invocationHandler);

由Proxy方法创建的动态代理类具有以下特点:

1) 动态代理类是public 、final 和非抽象类型的。

2)动态代理类继承了 java.lang.reflect.Proxy类;

3)动态代理类的名字以"$Proxy"开头

4) 动态代理类实现getProxyClass()和newProxyInstance()方法中的参数interface指定的所有接口

5)Proxy类的isProxy(Class<?> cl)静态方法用来判断指定的类是否为动态代理类。

6)动态代理类都具有一个Public类型的构造方法,该构造方法有一个InvocationHandler类型的参数

由Proxy静态方法创建动态代理类的实列有以下特点。

1)每一个动态代理类的实列都和一个InvocationHandler实列关联。Proxy类的getInvocationHandler(Object proxy)静态方法返回与参数proxy指定的代理类实列所关联的InvocationHandler对象。

2) 假定Subject有一个 save()方法,那么程序调用动态代理类实列subject的save()方法时,该方法会调用与他关联的InvocationHandler对象的invoke()方法。

InvocationHandler 接口为方法调用接口,它声明了负责调用任意一个方法的invoke()方法:

invoke(Object proxy, Method method, Object[] args) 

参数Proxy指定动态代理类的实列,参数method指定被调用的方法,参数args指定向被调用方法传递的参数,invoke()方法的返回值表示被调用方法的返回值。

1.ProxynewProxyInstance方法创建代理类的实列

  Proxy 提供用于创建动态代理类和实例的静态方法

package javaee.net.cn.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 *  动态代理演示
 *  通过分析代码可以看出Java 动态代理,具体有如下四步骤:
    通过实现 InvocationHandler 接口创建自己的调用处理器;
    通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
    通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
 */
public class Test{
    public static void main(String[] args) {
        //代理的真实对象
        Subject realSubject = new RealSubject();      
        InvocationHandler handler = new LogInterceptor(realSubject);
        ClassLoader loader = realSubject.getClass().getClassLoader();
        Class<?>[] interfaces = realSubject.getClass().getInterfaces();
        /**
         * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
         */
        Subject subjectProxy = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);      
        subjectProxy.save();      
    }
}

2.实现InvocationHandler接口 用来创建一个InvocationHandler对象。

package javaee.net.cn.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 调用处理器实现类
 * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
 */
public class LogInterceptor implements InvocationHandler{
    /**
     * 这个就是我们要代理的真实对象
     */
    private Object target;
    /**
     * 构造方法,给我们要代理的真实对象赋初值
     * @param subject
     */
    public LogInterceptor(Object target){
        this.target = target;
    }

    /**
     * 该方法负责集中处理动态代理类上的所有方法调用。
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
     * @param proxy  代理类实例
     * @param method 被调用的方法对象
     * @param args   调用参数
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("trancation start");

        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object returnValue = method.invoke(target, args);

        //在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("trancation commit");
        return returnValue;
    }
}

调用顺序:当我们调用subjectProxy 的save()方法 会进入 InvocationHandler实现类的invoke()方法,invoke()方法里面会执行realSubject的save()方法

下面是方法运行的结果

trancation start
insert into ......
trancation commit

像是Spring的事物吧。Spring AOP管理的事物,原理也是动态代理

这是对反射和classLoader的一些补充解释

遇到过一个面试题:RealSubject的save()方法里面调用的RealSubject其他方法,动态代理RealSubject类的时候调用save()方法会执行几次切面逻辑 ?

明显一次,当时没反应过来 还是对代码理解的不深刻。(不明白的可以留言)

Spring事物不生效的原因

对于JDK而言,它是要求被代理的目标对象必须拥有接口,而对于CGLIB则不做要求。默认情况下,Spring会安装一条这样的规则处理

当你需要使用AOP的类拥有接口时,它会以JDK动态代理运行,否则以CGLIB运行。

思考:Spring中如何强制使用CGLIB实现AOP?
 (1)添加CGLIB库
 (2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

@Transactional自调用失效的问题

    class UserService{
        @Transactional
        void A(){
            B();
        }
        @Transactional
        void B(){
        }
    }

我们在一个类userService的接口A,调用userService的另一个接口B。虽然接口A和接口B都加上了@Transactional注解。但是对于B接口而言 事物不生效(出现错误不会回滚)。

原因:Spring数据库事物的约定实现的原理是AOP,而AOP的原理是动态代理,在自调用的过程中是类自身的调用,而不是代理对象去调用,那么久不会产生AOP,

这样Spring就不能把你的代码织入到约定的流程中。

解决方法: 想办法把类调用其内部的方法编程代理类之间的调用,那就是 从IOC容器中再去获得一次类userService,此时获取的对象是Spring IOC容器中的代理对象

最后用新获取的对象执行接口B,此时接口A和接口B的@Transaction都会生效。

推荐一篇文章 Spring事物实现原理源码解读和事物的传播行为

JDK动态代理在RPC框架中的应用

https://www.cnblogs.com/jun1019/p/10952447.html

我们可以看到用JDK动态代理实现的RPC,客户端可以 以SDK的方式去调用Server端。

如果我们开发一个功能 一般都会提供两种方式调用

1.REST直接请求

2.利用动态代理 以SDK的方式接口调用(泛化调用:服务调用方不依赖服务方提供的API)

CGLIB实现动态代理

JDK的动态代理有一个缺点:被代理类需要实现某个接口 Proxy内部实现决定的(第二个参数需要是interfaces)

CGLI实现动态代理比JDK的简单,而且代理类不需要实现任何接口

package com.mashibing.dp.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Random;

/**
 * CGLIB实现动态代理不需要接口
 */
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Tank.class);
        enhancer.setCallback(new TimeMethodInterceptor());
        Tank tank = (Tank)enhancer.create();
        tank.move();
    }
}

class TimeMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        System.out.println(o.getClass().getSuperclass().getName());
        System.out.println("before");
        Object result = null;
        result = methodProxy.invokeSuper(o, objects);
        System.out.println("after");
        return result;
    }
}

class Tank {
    public void move() {
        System.out.println("Tank moving claclacla...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
View Code
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Tank.class);//生成的动态代理类是tank类的子类
        enhancer.setCallback(new TimeMethodInterceptor()); // 相当于jdk的invocationHandler
        Tank tank = (Tank)enhancer.create();
        tank.move();

CGLB和JDK的动态代理底层都是asm,ASM 直接操纵二进制字节码,有了asm之后 java才可以被称为动态语言(运行的时候 动态改类里面的属性和方法) 反射改不了属性和方法。

posted @ 2018-06-17 17:59  palapala  阅读(360)  评论(0编辑  收藏  举报