我的技术学习物语果然有问题
(最后更新 )

代理模式

代理模式顾名思义就是为一个对象提供一个代理,并让其他对象通过代理对象来控制这个对象。代理模式又分为两种,一种叫静态代理,一种叫动态代理。

什么是代理模式

代理模式顾名思义就是为一个对象提供一个代理,并让其他对象通过代理对象来控制这个对象。代理模式又分为两种,一种叫静态代理,一种叫动态代理。

静态代理

角色分析

  1. Isubject 抽象角色,一般使用接口和抽象类,不过抽象类不怎么好用,大多都是接口。
  2. RealSubject 真实角色,被代理的角色。
  3. Proxy 代理角色,代理真实角色

代码实现1

光用文字描述,会觉得十分的绕,还是用代码来讲解一下吧。首先先模拟一下环境,比如买火车票这件事情。卖火车票这件事情就是一个抽象角色,我们为它创建一个接口。

public interface buyTickets {
    void buytickets();
}

有买票的人也就会有卖的人,这里是卖票的设为火车站。这样的话火车站就是一个真实角色,为它创建一个类。

public class Station implements buyTickets {
    public void buytickets() {
        System.out.println("火车站在卖票");
    }
}

那么问题就来了,我们学生需要买票回家,是该怎么买呢?应该不会有多少人去到火车站买票吧。大多都是网上订票吧,就比如使用软件买票,方便也快捷,这个软件就是一个代理角色,代理了火车站卖票。我们也为他创建一个类。一般代理都会有很多特定的作用,即附属一些其他的方法,就比如使用软件卖票会十分方便。

public class Software implements buyTickets {
    private Station station;
    public  Software(){}
    public Software(Station station) {
        this.station = station;
    }
    public void buytickets() {
        station.buytickets();
    }
    public void Convenient(){
        System.out.println("用软件卖票十分方便");
    }
}

好了,那就模拟一下我们买票的过程吧。比如我们通过软件就能看到火车站在卖票,然后在软件上买票,这样就不用去到火车站在买票了,也就是我们不需要使用真实角色的卖票方法,使用的是代理对象的卖票方法,代理对象就会帮我们去真实对象中调用想要的方法(代理对象有点类似中介)。

public class test01 {
    public static void main(String[] args) {
     //首先是一个火车站要卖票
        Station station = new Station();
     //然后就是软件代理火车站卖票
        Software software = new Software(station);
        software.buytickets();
    }
}

代码实现2

上述是我们日常生活中一个小小的例子,可能没能够体会到代理模式的好处,我们来看看项目中的一些简单实现吧,比如经常见到的增删改查操作。首先是业务接口和接口实现类。

public interface userService {
    void add();
    void delete();
    void update();
    void select();
}
public class userServiceImpl implements userService{
    public void add() {
        System.out.println("增加了一个用户");
    }
    public void delete() {
        System.out.println("删除了一个用户");
    }
    public void update() {
        System.out.println("修改了一个用户");
    }
    public void select() {
        System.out.println("查询了一个用户");
    }
}

这是没有代理角色的情况,我们也可以直接创建userServiceImpl对象调用里面的方法,但是要是之后需要了增加一些业务需求,比如增加一个日志功能,我们常规就是直接修改userServiceImpl类,比如这样

public class userServiceImpl implements userService{
    public void add() {
        System.out.println("使用add方法增加了一个用户");
        System.out.println("增加了一个用户");
    }
    public void delete() {
        System.out.println("使用delete方法删除了一个用户");
        System.out.println("删除了一个用户");
    }
    public void update() {
        System.out.println("使用update方法修改了一个用户");
        System.out.println("修改了一个用户");
    }
    public void select() {
        System.out.println("使用select方法查询了一个用户");
        System.out.println("查询了一个用户");
    }
}

这只是四个增删改查的操作,你都会发现这样很麻烦,要是再加几个功能,或者再加几个操作,难道还要一个个改吗?这时候用代理就能很好的解决这种问题。那我们先创建这个代理类。

public class Proxy  implements userService{
    private userServiceImpl userserviceimpl;
    public Proxy() {}
    public Proxy(userServiceImpl userserviceimpl) {
        this.userserviceimpl = userserviceimpl;
    }
    public void add() {
        log("add");
        userserviceimpl.add();
    }
    public void delete() {
        log("delete");
        userserviceimpl.delete();
    }
    public void update() {
        log("update");
        userserviceimpl.update();
    }
    public void select() {
        log("select");
        userserviceimpl.select();
    }
    public void log(String logs){
        System.out.println("使用了"+logs+"方法");
    }
}
public class test01 {
    public static void main(String[] args) {
        userServiceImpl userserviceimpl = new userServiceImpl();
        Proxy proxy = new Proxy(userserviceimpl);
        proxy.add();
    }
}

当然有人会觉得代理也没差多少,也是挺麻烦的,都是需要手动添加很多代码,但代理模式可以更加方便地拓展,比如需要加功能就再添加一个方法,不用总是改接口实现类里的方法,代码一多就能感受到代理模式的方便了,随便改源码不是一个好的习惯。

动态代理

静态代理大概了解了,就能发现静态代理会有一个很大的问题。当需要代理的类多了,就会发现代理的类也会增多,因为静态代理是一个类绑定一个代理类的,这就很麻烦取管理了,因为代理类的创建就是为了方便,所以动态代理就出现了。动态代理的代理类是动态生成的,我们写好一个工具类就能动态生成很多类的代理类了。

动态代理分为两大类。

  1. JDK动态代理
  2. CGLib动态代理

JDK动态代理

JDK动态代理是Java自带的一种动态代理的方式,核心原理就是通过反射来实现的。首先要实现JDK的动态代理就要先了解一个类和一个接口,直接看文档的说法。

  1. Proxy Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有的动态代理类的超类。这里也不深入了解了,只要了解它主要的一个方法,生成动态代理类的方法。

    Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class<?>[] { Foo.class }, handler);

  2. InvocationHandler 每个代理实例都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用将会被编码分派到其调用处理程序的invoke方法。官方说法有点绕,简单来说是这个接口就只有一个invoke方法,这个方法是处理被代理实例即真实角色的,并会返回一个结果。

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

大概了解了这两个类,就可以写一个生成动态代理的工具类吧。(基本固定,可以当作万能的工具类)

public class ProxyInvocationHandler implements InvocationHandler {
    //设置一个被代理的对象
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }

    //生成代理类,文档的写法
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                      target.getClass().getInterfaces(),this);
    }

    //处理要被代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        return result;
    }
}

写完了代理类就来写测试一下吧,例子就选之前的那个增删改查的接口吧。

public class test01 {
    public static void main(String[] args) {
        //先创建一个真实角色
        userServiceImpl userservice = new userServiceImpl();
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //闯入需要代理的角色
        pih.setTarget(userservice);
        //生成一个代理类
        userService proxy = (userService) pih.getProxy();

        proxy.add();
        proxy.delete();
        proxy.select();
        proxy.update();
    }
}

当我们需要添加功能的时候,比如添加一个日志的功能,就改写一下工具类就好了。

public class ProxyInvocationHandler implements InvocationHandler {
    //设置一个被代理的对象
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }
    //生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                      target.getClass().getInterfaces(),this);
    }
    //处理要被代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }
    public void log(String msg){
        System.out.println("使用了"+msg+"方法");
    }
}

CGLib动态代理