栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA动态代理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA动态代理

最近在研究在spring项目中注入HttpServletRequest的时候遇到了一个动态代理的概念,这里了解下java中代理的演变

1. 需求场景

现在有这么一个场景,有一个Animal接口,接口中定义了一个eatMeat()方法,

public interface Animal {
    public void eatMeat();
}

一个Human类实现了Animal接口,并重写了eatMeat()方法

public class Human implements Animal {
    @Override
    public void eatMeat() {
        System.out.println("人类吃肉。。。");
    }
}

下面进行测试

public class Test {
    public static void main(String[] args) {
        Human human = new Human();
        human.eatMeat();
    }
}
人类吃肉。。。
2. 需求内容

现在这么一个需求,在执行eatMeat()方法之前打印对肉的处理方式,比如,烤熟了再吃。

3.解决方案 3.1 修改Human类中的方法

这个好办,我们修改对应的类里面的方法,在方法中添加烤肉的代码不就好了。

public class Human implements Animal {
    @Override
    public void eatMeat() {
        System.out.println("烤肉。。。");
        System.out.println("人类吃肉。。。");
    }
}

这种情况下,如果某个人(某个Human的实例)他就是不想烤肉,他就想直接吃肉,咋办,方法体已经被修改了,没办法兼顾了,这个方法,不可取。

3.2 调用eatMeat方法前后处理

这时候想到,既然不方便修改类,那我在调用方法之前添加烤肉的步骤不就好了,在实例调用eatMeat的方法的时候,在前面添加处理肉代码,你想加就加,不想加就不加,见下:

public class Test {
    public static void main(String[] args) {
        Human human = new Human();
        System.out.println("烤肉。。。");
        human.eatMeat();
        System.out.println("");
        Human human1 = new Human();
        human1.eatMeat();
    }
}
烤肉。。。
人类吃肉。。。

人类吃肉。。。

这种方法,不能说它不对,但是,如果吃了100次肉呢,我们添加100次烤肉的代码?1000次、10000次呢?这里的代码很少,如果不只是烤肉呢,把整个烹饪过程都加上去,需要100行代码,1000行代码,难道每次都要复制这么长的代码?这样很不友好,这个方法也不可取。

3.3 添加代理类(静态代理)

这也不让,那也不让,那我干脆新建一个类,在这个类里面持有要吃肉的Human实例(通过构造方法传入),然后定义一个方法,在这个方法中调用Human实例的eatMeat方法,然后在调用之前烤肉,如果你想烤肉再吃,就通过这个类来eatMeat,否则你就直接调用Human实例来eatMeat。

public class newHuman {
    Animcal human;
    public newHuman(Animcal human) {
        this.human = human;
    }
    public void eat(){
        System.out.println("烤肉。。。");
        human.eatMeat();
    }
}
public class Test {
    public static void main(String[] args) {
        Animcal human = new Human();
        Animcal human1 = new Human();
        NewHuman newHuman = new NewHuman(human1);
        human.eatMeat();
        System.out.println("");
        newHuman.eat();
    }
}

这就是代理的思想了,但是这里还不够完善。

代理类存在的意义应该是为被代理类做功能增强,而不应该修改被代理类的逻辑,如果这里eatMeat有返回值话,如果不限定的话,可能会导致代理类方法的返回值和被代理类方法的返回值类型不同之类的问题,我们需要限定他们的返回值一致,不能仅仅依靠程序员的细心。

此外,代理的目标是让客户端在使用代理对象的时候要像使用被代理对象一样。

我们可以让代理类也实现Animal接口,在其中重写eatMeat方法,在这个方法中调用持有的Human对象的eatMeat的方法,这样就可以保证他们的返回值是一致了,连方法名都一样了。

public class NewHuman implements Animal {
    Animcal human;
    public NewHuman(Animcal human) {
        this.human = human;
    }
    @Override
    public void eatMeat() {
        System.out.println("烤肉。。。");
        human.eatMeat();
    }
}
public class Test {
    public static void main(String[] args) {
        Animal human = new Human();
        Animal human1 = new Human();
        human1 = new NewHuman(human1);
        human.eatMeat();
        System.out.println("");
        human1.eatMeat();
    }
}

这样的话,结合多态的应用,客户端在使用代理对象的时候就像和是用被代理对象一样了,如上面的代码。

这个方案看起来靠谱多了,但是,如果需要被代理的对象很多呢,不止人类会在吃肉之前烤,可能猩猩/猴子甚至更多的动物也会烤肉再吃(假设),难道每个这个实现类都需要有这么一个烤肉的代理对象吗?这样会不会产生太多代理类了。

3.4 动态代理

在上面的场景中,有没有办法让所有的需要烤肉的类共用一个代理呢?答案是肯定的。

在java中动态代理主要是通过Proxy接口类实现的,每个Proxy接口的实现类都是一个代理对象,下面是动态代理的demo,Animal接口和Human类没有变化。

public class RoastInvocationHandler implements InvocationHandler {
    Object object;
    public RoastInvocationHandler(Object object) {
        this.object = object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("烤肉。。。");
        Object res = method.invoke(object, args);
        return res;
    }
}
public class Test {
    public static void main(String[] args) {
        Animal human = new Human();
        human = (Animal)Proxy.newProxyInstance(
            human.getClass().getClassLoader(), 
            human.getClass().getInterfaces(), 
            new RoastInvocationHandler(human));
        human.eatMeat();
    }
}

这里通过Proxy的静态方法newProxyInstance创建一一个代理对象,传递的三个参数分别的被代理对象的类加载器,被代理对象的接口列表,以及一个自定义的调度处理器(字面翻译的),

类加载器不需要太纠结,保证代理对象和被代理对象用的类加载器一致(笔者也不是很清楚。。。)。

被代理对象实现的接口列表,代理对象也会根据这个列表实现相同的接口。

主要是调度处理器,我们在Proxy的源码,类的介绍中看到这一段描述,简单翻译了一下:

A dynamic proxy class (simply referred to as a proxy class below) is a class that implements a list of interfaces specified at runtime when the class is created, with behavior as described below. 
一个动态代理类是一个在创建的时候实现了一系列的接口的类。
A proxy interface is such an interface that is implemented by a proxy class. 
代理接口是被代理类实现的接口。
A proxy instance is an instance of a proxy class. 
代理实例是代理接口的实例。
Each proxy instance has an associated invocation handler object, which implements the interface InvocationHandler. 
每一个代理实例都关联了一个InvocationHandler接口的实现类
A method invocation on a proxy instance through one of its proxy interfaces will be dispatched to the invoke method of the instance's invocation handler, passing the proxy instance, a java.lang.reflect.Method object identifying the method that was invoked, and an array of type Object containing the arguments. 
一个代理实例在调用它实现的代理接口的方法的时候,这个操作会转而去调用关联的InvocationHandler接口的实现类的invoke方法,传递代理实例对象,调用的方法,方法的参数数组。

重点关注第五句,Proxy的实现类在调用被代理对象的方法的时候会转而去调用关联的InvocationHandler接口的实现类的invoke方法,传递的参数是代理类实例,调用的方法以及方法的参数。

在当前的例子中,我们将代理对象强转为了Animal接口的实例,前面提到了,代理对象会实现和被代理对象相同的接口,所以这里是可以直接强转的。

而这里human.eatMeat()在执行的时候会转而去调用RoastInvocationHandler中的invoke方法,而在invoke方法中,我们通过反射执行了传进来的被代理对象的eatMeat方法Object res = method.invoke(object, args),也添加了增强的逻辑(烤肉)System.out.println("烤肉。。。"),实现了功能增强(吃肉之前烤肉)。

Animal不同的实现类都可以通过这个方式来进行功能增强(吃肉前烤肉),这里基本就是动态代理的实现了。

下面多创建几个Animcal的实现类进行测试:

public class Dog implements Animal{
    @Override
    public void eatMeat() {
        System.out.println("狗吃肉");
    }
}
public class Monkey implements Animal{
    @Override
    public void eatMeat() {
        System.out.println("猴子吃肉。。。");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal human = new Human();
        Animal monkey = new Monkey();
        Animal dog = new Dog();
        dog = (Animal)Proxy.newProxyInstance(
            dog.getClass().getClassLoader(), 
            dog.getClass().getInterfaces(),
            new RoastInvocationHandler(dog));
        human = (Animal)Proxy.newProxyInstance(
            human.getClass().getClassLoader(), 
            human.getClass().getInterfaces(), 
            new RoastInvocationHandler(human));
        monkey = (Animal)Proxy.newProxyInstance(
            monkey.getClass().getClassLoader(), 
            monkey.getClass().getInterfaces(),
            new RoastInvocationHandler(monkey));
        human.eatMeat();
        System.out.println("");
        monkey.eatMeat();
        System.out.println("");
        dog.eatMeat();
    }
}
烤肉。。。
人类吃肉。。。

烤肉。。。
猴子吃肉。。。

烤肉。。。
狗吃肉

以上都是个人的理解,内容如有错误的地方,欢迎指正,谢谢。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/354087.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号