前言

代理模式主要用途就是能够动态地在方法的前后扩展附加一些新的功能,它能够在不破坏原有类的封装的前提下,增强相应的功能。代理模式是面向切面编程的理论基础,代理模式广泛地应用在软件开发中。代理模式实现方式包括静态代理和动态代理(反射代理和cglib代理),这两种模式都是在被代理对象持有一个被代理对象,通过被代理对象调用代理对象的方法。以下以记录学生活动日志为例,在执行学生类中每个方法后记录该方法的日志记录作为示例,介绍代理模式。

静态代理

我们封装一个学生类,该类包括上课、考试等方法,通过代理类附加日志功能。实现类图如下:
在这里插入图片描述
学生接口代码如下:

public interface IStudent {
    void exam(String stuName,String methodName);
    void gotoClass(String stuName,String methodName);
}

学生类实现了学生接口,封装学生考试和上课两个方法,代码如下:

public class Student implements IStudent {
    @Override
    public void exam(String stuName,String methodName) {
        System.out.println(stuName+"考试作答过程....");
    }

    @Override
    public void gotoClass(String stuName,String methodName) {
        System.out.println(stuName+"上课做笔记、听讲过程....");
    }
}

设计代理类,代理类同样需要实现学生接口,同时在代理类中为考试和上课附加日志的功能,代码如下:

public class StudentProxy implements IStudent {
    private Student student;

    public StudentProxy(Student stu){
        student = stu;
    }

    @Override
    public void exam(String stuName,String methodName) {
        student.exam(stuName,methodName);
        log(stuName,methodName);

    }

    @Override
    public void gotoClass(String stuName,String methodName) {
        student.gotoClass(stuName,methodName);
        log(stuName,methodName);
    }

    private void log(String stuName,String oprationName){
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
        String tm = df.format(new Date());
        System.out.println(stuName+"于"+tm+"参加了"+oprationName+";" );

    }
}

测试代码如下:

@Test
    public void exam(){
      Student student = new Student();
      StudentProxy studentProxy = new StudentProxy(student);

      studentProxy.exam("张三","考试");
      studentProxy.gotoClass("张三","上课");
    }

运行结果如下:
在这里插入图片描述

使用静态代理,我们在不破坏封装的前提下,给学生类中的方法增加了日志输出功能,上述示例将日志输出到控制台上,我们也可以将日志输出到日志文件中。这样做的好处,学生类原有的封装不变,动态的附加了日志功能,学生类只关注自己本身的业务功能,遵循了单一职责原则,职责清晰。
静态代理的缺点是每个代理类需要程序员写一个代理类,如果一个项目包含很多类,程序员的任务很重,而且代理类中代码大部分相同,如果能将这些相同的代码提取出来复用,则工作量和出错的几率就会小很多,这样就产生了动态代理

动态代理

动态代理和静态代理一样,都必须有一个代理类,客户端调用代理对象完成功能附加,不同的是静态代理类是程序员编写,而动态代理对象是由一个代理工厂生产出来的,这个代理工厂能够生产所有类的代理对象。代理工厂通常使用类的反射机制来完成,使用反射机制完成代理工厂的实现,也叫反射代理,java中叫反射代理。
接口和学生类代码不变,代理工厂代码如下:

//动态代理,动态代理不需要实现接口,但是需要指定接口类型;
// 代理对象的生成,是利用反射技术实现的
public class ProxyFactory {
    //维护一个目标对象
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }

    //给目标对象生成代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        log(args[0].toString(),args[1].toString());
                        return returnValue;
                    }
                }
        );
    }

    private void log(String stuName,String oprationName){
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
        String tm = df.format(new Date());
        System.out.println(stuName+"于"+tm+"参加了"+oprationName+";" );

    }
}

其中:Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)的三个参数含义
loader:使用那个类加载器加载代理对象;interfaces:是代理类要实现的接口;h:是代理方法在执行时,会调用h里面的invoke方法去执行

测试代码如下:

public class StudentProxyTest {
    @Test
    public void dt(){
        Student target = new Student();
        // 【原始的类型 class cn.itcast.b_dynamic.UserDao】
        System.out.println(target.getClass());

        // 给目标对象,创建代理对象
        IStudent proxy = (IStudent) new ProxyFactory(target).getProxyInstance();
        // class $Proxy0   内存中动态生成的代理对象
        System.out.println(proxy.getClass());

        // 执行方法   【代理对象】
        proxy.exam("张三","考试");
        proxy.gotoClass("张三","上课");
    }
}

运行结果如下:
在这里插入图片描述
运行结果与静态代理实现结果相同,代理对象由代理工厂创建。加入教师上课、考试也需要记录日志,程序员只需要关心具体的业务,代理工厂代码无需变动也可以实现。代码如下:
教师接口和教师实现类代码如下:

public interface ITeacher {
    void invigilate(String stuName,String methodName);
    void lecture(String stuName,String methodName);
}

public class Teacher implements ITeacher {
    @Override
    public void invigilate(String stuName, String methodName) {
        System.out.println(stuName+"考试监考过程....");
    }

    @Override
    public void lecture(String stuName, String methodName) {
        System.out.println(stuName+"讲课过程....");
    }
}

测试代码如下

 @Test
    public void teacherTest(){
        ITeacher target = new Teacher();
        // 【原始的类型 class cn.itcast.b_dynamic.UserDao】
        System.out.println(target.getClass());

        // 给目标对象,创建代理对象
        ITeacher proxy = (ITeacher) new ProxyFactory(target).getProxyInstance();

        // 执行方法   【代理对象】
        proxy.invigilate("李四","考试");
        proxy.lecture("李四","上课");
    }

执行结果:
在这里插入图片描述
动态代理工厂创建了教师类的代理对象。

反射代理是需要被代理类实现一个接口,通过接口完成代理对象的创建。然而,有一些实现类没有定义接口,能否实现动态代理?此时就需要使用CGLIB代理,CGlib代理实际上是通过拦截器完成的,再次不再展开。

小结

代理模式,尤其动态代理在软件开发中经常使用的一种模式,如:MyBatis实现机制的核心采用代理模式,SpringAOP本质也是代理模式。其本质是通过代理对象持有一个被代理对象的引用,在调用被代理对象方法时,附加一些功能。然而由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,但类的封装性得到了保证。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐