欢迎访问:我的个人网站
Java 动态代理
动态代理机制是指通过代理类,接口与具体的实现类并不直接产生关系,而是在运行期间产生关联。大致的流程如下:
- 创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。
- 创建一个代理类(wrapper)同时使其也实现这个接口。在代理类中持有一个被代理对象的引用。
- 在代理类方法中调用该对象的方法。代理类方法里面可以添加一些新的功能。
整个流程大致如下:
在Java中执行动态代理操作的类主要位于Java.lang.reflect包下,进一步的话,可以分为以下的两个类:
1.接口InvocationHandler
接口中仅仅定义了一个方法:
Object invoke(Object obj, Method method, Object[] args);
在使用该接口的时候,需要注意各个参数的含义,obj指代理类,method指被代理的方法,args则为被代理方法的参数数组。
2. 动态代理类Proxy
该类就是动态代理类,代理实现了InvocationHandler接口的代理类,主要有以下函数:
//构造一个代理类
protected Proxy(InvocationHandler handler);
//获得一个代理类,其中loader是类装载器,该装载器对代理类进行装载。
//interfaces是被代理类所需要实现的所有接口的集合,当传递之后,被代理类就宣称实现了所有给定的接口,也自然而然可以调用接口中的方法。
static Class getProxy(ClassLoader loader, Class[] interfaces)
//返回代理类的一个实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, Invocationhandler handler);
从非动态代理的例子说起
这里展示了一个非动态代理时的例子,循序渐进的过度到动态代理,也更容易接受,明白差异具体存在哪些地方。
场景:所有小动物都会吃,我们定义一个叫的接口,并找来两只具体的小动物去实现该接口叫的行为。然后我们打算对叫这个过程进行增强。
定义接口:
public interface Action {
void shout();
}
定义具体实现类:
public class Dog implements Action{
@Override
public void shout() {
System.out.println("汪汪汪...");
}
}
public class Cat implements Action {
@Override
public void shout() {
System.out.println("喵喵喵...");
}
}
调用:
Dog dog = new Dog();
dog.shout();
Cat cat = new Cat();
cat.shout();
输出:
汪汪汪...
喵喵喵...
很简单的一个流程,那么接下来,如果我们要考虑分别对这两只动物的“叫”方法进行增强,那么可以引入两个包装类, 分别对方法进行增强,那么上述的流程将转换为下面这样:
新增狗狗包装类, 猫猫包装类:
public class DogWrapper {
Dog dog;
public DogWrapper(Dog dog) {
this.dog = dog;
}
public void enhanceShout(){
System.out.println("小偷来啦!!!");
dog.shout();
System.out.println("小偷跑了...");
}
}
public class CatWrapper {
Cat cat;
public CatWrapper(Cat cat){
this.cat = cat;
}
public void enhanceShout(){
System.out.println("主人来啦");
cat.shout();
System.out.println("主人走了...");
}
}
调用:
DogWrapper dogWrapper = new DogWrapper(new Dog());
CatWrapper catWrapper = new CatWrapper(new Cat());
dogWrapper.enhanceShout();
catWrapper.enhanceShout();
输出:
小偷来啦!!!
汪汪汪...
小偷跑了...
主人来啦
喵喵喵...
主人走了...
以上就是不使用动态代理时候对一个方法进行增强的过程,流程很简单,但是问题也是非常明显的,如果要为Action接口添加新的行为,那么所有实现了该接口的类都必须同步增加对方法的实现。如果增加5个,10个…缺点是很明显的。
虽然可以考虑将DogWrapper继承于Dog类而不再实现Action接口,但是很明显这样会严重限制DogWrapper的适用范围,虽然在本例中,确实是对每个具体的动物创建了一个具体的包装类以单独进行增强。但是实际上有时候会有一个单独的Wrapper对很多实现了某一接口的类进行通用性的增强,所以将Wrapper归结到某个具体类下,这种方式也还是行不通的。所以也就需要使用动态代理了…
转换为动态代理形式再次实现
如果使用动态代理去实现的话,那么 要包装/增强的类必须实现一个接口, 并且该接口里面定义了所有准备在包装器里面使用的方法。
1.首先建立一个代理类以进行对动物叫声的增强
此时就不用实现之前的Action接口转而实现接口:InvocationHandler。该代理类实现了InvocationHandler接口并在要实现的方法里面添加了增强,并且在实例化一的时候,要求指定被代理的对象。
public class ActionWrapper implements InvocationHandler{
//被代理对象
private Object proxyed;
public ActionWrapper(Object obj){
this.proxyed = obj;
}
@Override
public Object invoke(Object obj, Method method, Object...params) throws Throwable{
//前置增强内容
System.out.println("饿了...");
Object r = method.invoke(this.proxyed, args);
//后置增强内容
System.out.println("得到食物...");
return r;
}
}
2.通过Proxy类实例化一个动态代理对象
在建立完代理类之后,需要借助Proxy的newProxyInstance方法来实例化一个动态代理对象并执行后续的操作。流程如下:
需要注意的是,实例化的代理对象需要强转为 被代理对象所实现的接口类型,而非被代理对象的类型。
//建立被代理对象
Dog dog = new Dog();
//创建一个代理类
ActionWrapper actionWrapper = new ActionWrapper(dog);
/*
借助Proxy类来实例化一个代理类的对象
需要传递:
1.被代理对象的类加载器。
2.被代理对象所实现的所有接口集合。
3.代理类。
转换为Action类型!
*/
Action action = (Action) Proxy.newProxyInstance(Dog.class.getClassLoader(), Dog.class.getInterfaces(), actionWrapper);
//执行方法,此时执行的结果就是增强后的效果了
action.shout();
输出:
饿了...
汪汪汪...
得到食物...
上面实例化一个代理类对象的过程大致为:告诉Proxy类用一个指定的类加载器动态的创建一个对象。该对象实现了指定的接口,并提供了InvocationHandler来替代原来方法的主体。在ActionWrapper代理类里面没有任何对Action接口的引用。在实例化这个代理类的时候,指定了被代理的对象。而被代理对象中的所有方法都会由代理类的invoke方法接管。
可以尝试:在Action中新增一个方法并让实现类(Dog)进行实现,那么ActionWrapper仍旧可以拦截该方法并为其添加增强。同理对方法名进行更改也如此…
抽离为一个简单的AOP容器
动态代理的形式与Spring框架的AOP特性非常相像,实际上Spring的AOP底层实现就是基于上面说到的动态代理。利用这一点,可以很轻易对多个业务逻辑进行拦截添加额外的功能(安全,食物,日志,过滤…)
这里简单的将上诉的动态代理抽离为一个容器:
class AOPContainer{
public static Object getBean(Object action){
ActionWrapper actionWrapper = new ActionWrapper(action);
return Proxy.newProxyInstance(action.getClass().getClassLoader(), action.getClass().getInterfaces(), actionWrapper);
}
}
后续所有的类在实现了一个接口的前提下,均可以使用该容器进行增强:
public interface A{
void aa();
}
public class AA implements A{
public void aa(){
System.out.println("测试");
}
}
public static void main(String...args){
AA a = new AA();
A b = (A) AOPContainer.getBean(a);
b.aa();
}
输出:
饿了...
测试
得到食物...