一、代理的作用
代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展。举个例子来说明代理的作用:明星与经纪人之间就是被代理和代理的关系,明星出演活动的时候,明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)。
但是往往你又不能直接对源代码进行修改,可能是你希望原来的对象还保持原来的样子,又或许你提供的只是一个可插拔的插件,甚至你有可能都不知道你要对哪个目标对象进行扩展。这时就需要用到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!