Java(十)静态代理和动态代理

一、代理的作用
代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展。举个例子来说明代理的作用:明星与经纪人之间就是被代理和代理的关系,明星出演活动的时候,明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)。
但是往往你又不能直接对源代码进行修改,可能是你希望原来的对象还保持原来的样子,又或许你提供的只是一个可插拔的插件,甚至你有可能都不知道你要对哪个目标对象进行扩展。这时就需要用到java的代理模式了。
代理的第一个好处就是解耦:比如一个对象不想或者不能直接引用另一个对象,这个时候就可以通过中间代理对象来访问另一个对象,避免了两个对象的代码耦合。
代理的第二个好处就是增强,代理对象包含了目标对象的引用,同时代理对象还可以对业务逻辑进行增强。

java有三种代理模式,静态代理,JDK代理和Cglib代理。

二、静态代理
静态代理比较简单,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。

1 public interface ISinger {
 2     void sing();
 3 }
 4 
 5 /**
 6  *  目标对象实现了某一接口
 7  */
 8 public class Singer implements ISinger{
 9     public void sing(){
10         System.out.println("唱一首歌");
11     }  
12 }
13 
14 /**
15  *  代理对象和目标对象实现相同的接口
16  */
17 public class SingerProxy implements ISinger{
18     // 接收目标对象,以便调用sing方法
19     private ISinger target;
20     public UserDaoProxy(ISinger target){
21         this.target=target;
22     }
23     // 对目标对象的sing方法进行功能扩展
24     public void sing() {
25         System.out.println("向观众问好");
26         target.sing();
27         System.out.println("谢谢大家");
28     }
29 }
/**
 2  * 测试类
 3  */
 4 public class Test {
 5     public static void main(String[] args) {
 6         //目标对象
 7         ISinger target = new Singer();
 8         //代理对象
 9         ISinger proxy = new SingerProxy(target);
10         //执行的是代理的方法
11         proxy.sing();
12     }
13 }

总结:其实这里做的事情无非就是,创建一个代理类SingerProxy,继承了ISinger接口并实现了其中的方法。只不过这种实现特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。假使代理对象中只是简单地对sing方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所以这里的包含是关键。
  缺点:这种实现方式很直观也很简单,但其缺点是代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。
 
三、动态代理
 代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(Proxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。动态代理分为JDK代理和Cglib代理两种。
(一)JDK代理
JDk动态代理机制中有一个重要的类和一个接口Proxy(类)和InvocationHandler(接口),是我们实现动态代理的核心;
1.Proxy类
Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。

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

这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:
(1)loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载.通常用被代理的类名.getClass().getClassLoader()来获取。
(2)interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。通常由 被代理的类名.getClass().getInterface()来获取。
(3)h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

2.InvocationHandler接口
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
InvocationHandler是一个函数式接口,只含有一个抽象的方法invoke。

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;

InvocationHandler接口的invoke方法是系统自己调用,不是程序员显示调用。其中proxy参数和method参数的赋值也是系统完成的。proxy是被代理的对象的实例,method是实例调用的方法,具体如下例所示:
Subject subject = (Subject)Proxy.newInstance(…)
subject.prim()
这里subject是被代理的对象,会给proxy赋值,prim()就会给method赋值

3.JDK代理的实例
定义一个接口People

public interface People {
    public String work();
}

定义一个Teacher类:
public class Teacher implements People{
    @Override
    public String work() {
        System.out.println("老师教书育人...");
        return "教书";
    }

}

定义一个代理处理程序:

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

public class WorkHandler implements InvocationHandler{
    //代理类中的真实对象  
    private Object obj;
    public WorkHandler() {
        // TODO Auto-generated constructor stub
    }
    //构造函数,给我们的真实对象赋值
    public WorkHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在真实的对象执行之前我们可以添加自己的操作
        System.out.println("before invoke。。。");
        Object result = method.invoke(obj, args);
        //在真实的对象执行之后我们可以添加自己的操作
        System.out.println("after invoke。。。");
        return result;
    }

}

测试类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        //要代理的真实对象
        People people = new Teacher();
        //代理对象的调用处理程序,我们将要代理的真实对象传入代理对象的调用处理的构造函数中,最终代理对象的调用处理程序会调用真实对象的方法
        InvocationHandler handler = new WorkHandler(people);//用对象给Obj变量赋值
        /**
         * 通过Proxy类的newProxyInstance方法创建代理对象,我们来看下方法中的参数
         * 第一个参数:people.getClass().getClassLoader(),使用handler对象的classloader对象来加载我们的代理对象
         * 第二个参数:people.getClass().getInterfaces(),这里为代理类提供的接口是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法
         * 第三个参数:handler,我们将代理对象关联到上面的InvocationHandler对象上
         */
        People proxy = (People)Proxy.newProxyInstance(handler.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);
        //System.out.println(proxy.toString());
        System.out.println(proxy.work());
    }
}

结果如下:
before invoke。。。
老师教书育人…
after invoke。。。
教书

补充:与JDK代理相关的函数
1.getInvocationHandler:返回指定代理实例的调用处理程序
2.getProxyClass:给定类加载器和接口数组的代理类的java.lang.Class对象。
3.isProxyClass:当且仅当使用getProxyClass方法或newProxyInstance方法将指定的类动态生成为代理类时,才返回true。
4.newProxyInstance:返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

(二)Cglib代理
JDK代理(也叫Java动态代理)有一个点就是被代理的类必须要实现了接口,如果某个类什么接口都没有实现,那么只能用Cglib代码。但是,Cglib代理也有一个点就是不能代理被声明为final的类,因为这种类不能被继承,而CGLib原理是动态生成被代理类的子类。
CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。

原理:
(1)写一个实现MethodInterceptor接口的拦截器,在intercept方法中写我们想要加入的内容,方法返回值是调用父类的方法。
(2)代理类中调用Enhancer类,Enhancer类会为被代理类生成一个子类,并且执行子类的方法,执行方法时会被拦截器拦截,执行我们要的intercept方法,然后intercept方法里面最后调用了被代理类的方法,完成全部功能。

来看示例,假设我们有一个没有实现任何接口的类HelloConcrete:

public class HelloConcrete {
    public String sayHello(String str) {
        return "HelloConcrete: " + str;
    }
}


// CGLIB动态代理
// 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
class MyMethodInterceptor implements MethodInterceptor{
  ...
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        logger.info("You said: " + Arrays.toString(args));
        return proxy.invokeSuper(obj, args);
    }
}

//2.代理类:使用Enhancer类来进行代理,Enhancer类创建了HelloConcrete类的子类
class Daili{
    public static void main(String[] args){
        // 2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象。
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloConcrete.class);
        enhancer.setCallback(new MyMethodInterceptor());
        
        HelloConcrete hello = (HelloConcrete)enhancer.create();
        System.out.println(hello.sayHello("I love you!"));
    }
}

运行上述代码输出结果:
日志信息: You said: [I love you!]
HelloConcrete: I love you!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值