设计模式--代理模式

转:https://blog.csdn.net/cmavb/article/details/90720386

代理模式是Java常见的设计模式之一。所谓代理模式是指通过代理对象来访问被代理对象 ,可以在被代理对象的基础之上,增强额外的功能,而不需要修改别人已经写好的代码。
为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司、车票代售点代售车票等。

代理模式的实现

代理模式可以有两种实现的方式,一种是静态代理类,另一种是各大框架都喜欢的动态代理。下面我们主要讲解一下这两种代理模式

1.静态代理

静态代理在使用时,需要定义接口或者父类,目标对象与代理对象一起实现相同的接口或者是继承相同父类。

/**
 * 接口
 */
public interface IUserDao {
 
    void save();
}
/**
 * 接口实现
 * 被代理对象(目标对象)
 */
public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已经保存数据!----");
    }
}
/**
 * 代理对象,静态代理
 */
public class UserDaoProxy implements IUserDao{
    //接收保存目标对象
    private IUserDao target;
    public UserDaoProxy(IUserDao target){
        this.target=target;
    }
 
    public void save() {
        System.out.println("开始事务...");
        target.save(); //代理对象的方法中包含了被代理对象的方法,执行被代理对象的方法
        System.out.println("提交事务...");
    }
}
/**
 * 测试类
 */
public class App {
    public static void main(String[] args) {
        //目标对象
        UserDao target = new UserDao();
 
        //代理对象,把目标对象传给代理对象,建立代理关系
        UserDaoProxy proxy = new UserDaoProxy(target);
 
        proxy.save();//执行的是代理对象的方法
    }
}

总结:代理对象特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。这里的包含是关键。

静态代理的缺点:

1、需增加的代理类太多

2、被实现的接口如果增加了方法,被代理对象与代理对象都需要修改

2.动态代理

在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Srping的AOP(切面编程)。可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。

2.1 基于接口的动态代理(JDK官方)

动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)。代理对象不需要实现接口,需要指定接口类型。但是目标对象一定要实现接口,否则不能用动态代理。

代理类所在包:java.lang.reflect.Proxy

JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

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

三个参数的意义是:

ClassLoader loader:指定当前目标对象使用的类加载器,也就是代理对象和被代理对象使用相同的类加载器

Class<?>[] interfaces:目标对象实现的接口的类型。也就是代理对象和被代理对象具有相同的行为。实现相同的接口。

InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

public interface IActor {
    /**
     * 基本演出
     * @param time 表演时长
     */
    public void basicAct(float time);
 
    /**
     * 危险演出
     * @param time 表演时长
     */
    public void dangerousAct(float time);
}
/**
  * 目标对象
*/
public class Actor implements IActor {
 
    /**
     * 基本演出
     *
     * @param time 表演时长
     */
    @Override
    public void basicAct(float time) {
        System.out.println("基本表演,时长为:" + time);
    }
 
    /**
     * 危险演出
     *
     * @param time 表演时长
     */
    @Override
    public void dangerousAct(float time) {
        System.out.println("危险表演,时长为:" + time);
    }
}
/**
  *测试类,在本类中实现了代理对象
    */
public class ClientTest {
 
    public static void main(String[] args){
        // 生成目标对象,此处和静态代理的目的不一样,所以不是生成IActor的实例
        final Actor actor = new Actor();  // 内部类调用外部成员变量,必须为final
 
        // 获取代理对象
        IActor proxy = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(),
                                                      actor.getClass().getInterfaces(),
                                                      new InvocationHandler() {
           /**
            *   执行目标对象的任何方法,都会经过该方法。
            *   此方法有拦截的功能。
            * @param proxy 代理对象的引用。不一定每次都用得到
            * @param method 当前执行的方法对象
            * @param args  执行方法所需的参数
            * @return 前执行方法的返回值
            * @throws Throwable
            */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String name = method.getName(); // 目标对象执行了接口声明的任意方法都会调用本invoke方法,此处获得是调用了哪个方法名return method.invoke(actor, time);//执行目标对象的方法
      } }); 

    // 开始测试
    proxy.basicAct(100);
  }
}

上面的测试类中,明明代理对象已经是IActor类型的,为什么还要使用第二个getInterfaces()参数呢?如果看下面这个例子就能明白了:

/**
 * 创建动态代理对象
 * 动态代理不需要实现接口,但是需要指定接口类型
 */
public class ProxyFactory{
 
    //维护一个目标对象
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }
 
   //给目标对象生成代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), // 此处不知道目标对象的接口
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("开始事务2");
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事务2");
                        return returnValue;
                    }
                }
        );
    }
 
}

2.2 基于子类的动态代理(第三方CGLib)

有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib子类代理实现步骤:

1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可。

2.引入功能包后,就可以在内存中动态构建子类

3.目标类不能为final,否则报错。(因为final修饰的类不能被继承)

4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

//还是之前的目标对象,但是没有实现接口
public class Actor {
 
    /**
     * 基本演出
     *
     * @param time 表演时长
     */
    @Override
    public void basicAct(float time) {
        System.out.println("基本表演,时长为:" + time);
    }
 
    /**
     * 危险演出
     *
     * @param time 表演时长
     */
    @Override
    public void dangerousAct(float time) {
        System.out.println("危险表演,时长为:" + time);
    }
}
//测试类,包含了生成的代理对象
public class Client {
    /**
     * 基于子类的动态代理
     * 要求:
     * 目标类不能用final修饰
     * 用到的类:
     * Enhancer
     * 用到的方法:
     * create(Class, Callback):创建参数Class的子类对象
     * 方法的参数:
     * Class:被代理对象的字节码
     * Callback:如何代理
     *
     * @param args
     */
    public static void main(String[] args) {
        final Actor actor = new Actor();
        Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
                new MethodInterceptor() {
                    /**
                     * 执行目标对象的任何方法,都会经过该方法。在此方法内部就可以对目标对象的任何
                     方法进行增强。
                        在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自            
                        己的代理逻辑,类似于JDK中的InvocationHandler接口
                     *
                     * 参数:
                     * 前三个和基于接口的动态代理是一样的。
                     * proxy为:由CGLib动态生成的代理类实例
                     * MethodProxy:当前执行方法的代理对象,也就是生成的代理类对方法的代理引用。
                     * 更通俗的说,MethodProxy是cglib生成用来代替Method对象的一个对象,使用 
                     * MethodProxy比调用JDK自身的Method直接执行方法效率会有提升。
                     * 返回值:
                     * 当前执行方法的返回值
                     */
                    @Override
                    public Object intercept(Object proxy, Method method, Object[] args,
                                            MethodProxy methodProxy) throws Throwable {
                        String name = method.getName();
                        Float time = (Float) args[0];
                        Object rtValue = null;
                        if ("basicAct".equals(name)) {
                            //基本演出
                            if (time > 2000) {
                                rtValue = method.invoke(actor, time / 2);
                            }
                        }
                        return rtValue;
                    }
                });
        cglibActor.basicAct(10000);
    }
}

Spring AOP 怎么应用的动态代理

在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理。
如果目标对象没有实现接口,用Cglib代理。

原文地址:https://www.cnblogs.com/jvStarBlog/p/11042647.html