代理模式

时间:2024-03-01 21:27:08

代理模式

定义:

为实际对象提供一个代理,以控制对实际对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托执行后的后续处理。

image

一、静态代理

废话不多说,上例子。现在业务要求实现对学生信息的新增和修改功能,这个功能交到了小明的称手上,而小明很快的完成了代码的开发:

1、首先编写了一个接口:

public interface StudentService {
    
    public int addStudent(Student student);
    
    public int updateStudent(Student student);
}

2、然后再编写接口的实现类

public class StudentServiceImpl implements StudentService {
    
    public int addStudent(Student student) {
        // TODO 在这里实现了把学生的信息增加到数据库中
        System.out.printLn("在这里实现了把学生的信息增加到数据库中");
        // 伪代码
        int retult = studentRepository.insertStudent(student);
    }
    
    public int updateStudent(Student student) {
        // TODO 在这里实现了把学生的信息更新到数据库中
        System.out.printLn("在这里实现了把学生的信息更新到数据库中");
        // 伪代码
        int retult = studentRepository.updateStudent(student);
    }
}

写完之后就可以给到调用层调用了。

因为这个功能,小明完成得非常的好,还拿了年度最佳员工大奖。

过了不久,上级领导来审查代码,发现小明这个功能代码竟然没有记录日志,这是个大问题。于是小明就想立刻的在StudentServiceImpl的实现类里面增加上日志。但是这个领导又说了,不能在StudentServiceImpl类里面修改。于是这个时候,小明熟悉的静态代理模式就上场了。

3、使用静态代理模式来进行代码的增强,创建一个代理类

代理、代理,既然是代理,那么这个代理类肯定是和幕后的执行者存在一定的关联的,幕后执行者有的功能,代理类也要有,如果没有,那就不叫代理类了。

  • 在这里,幕后的执行者是StudentService接口,此接口有新增和修改Student信息的功能,所以我们的代理类也要有这两个接口。那代理类如何有相同的增加和修改学生信息的功能呢?答案就是代理类也实现StudentService接口。

    //实现StudentService接口以获得相同的实现方法,但是这里不是做为实际的业务处理。
    public class StudentServiceProxy implements StudentService {
        
        public int addStudent(Student student) {
            
        }
        
        public int updateStudent(Student student) {
            
        }
    }
    
  • 这样我们得到的第一步,但是这个代理类不是真正的业务实现类,它是要把业务逻辑的操作转发给真正的执行者。很显然,现在还没有这个执行者,所以第二步,把执行者加上。

所以我们这样来创建:

//实现StudentService接口以获得相同的实现方法,但是这里不是做为实际的业务处理。
public class StudentServiceProxy implements StudentService {
    
    //声明实际的业务处理者
    private StudentService studentService;
    //构造器注入后,此代理类StudentServiceProxy就可以在自身调用studentService的实现业务处理方法,
    //就具有了代理的功能了
    public StudentServiceProxy(StudentService studentService) {
        this.studentService = studentService;
    }
    
    public int addStudent(Student student) {
        //代理类做的事情(增强)
        beforeLog(student);
        
        //真正做业务处理的幕后者
        studentService.addStudent(student);
        
        //代理类做的事情(增强)
        afterLog(student);
    }
    
    public int updateStudent(Student student) {
        //代理类做的事情(增强)
        beforeLog(student);
        
        //真正做业务处理的幕后者
        studentService.updateStudent(student);
        
        //代理类做的事情(增强)
        afterLog(student);
    }
    
    //假设正面是一些日志的方法(代理类增加的一些功能)
    private void beforeLog(Student student) {
        System.out.println("请求的参数:" + studetn.toString);
        System.out.println(String.format("请求开始的时间:", new Date()));
    }
    
    private void afterLog(Student student) {
        System.out.println(String.format("请求结束的时间:", new Date()));
    }
    
}

4、调用层调用

public class StudentController {
    
    public static void main(String[] args) {
        StudentServiceImpl studentServiceImpl = new StudentServiceImpl();
        StudentServiceProxy proxy = new StudentServiceProxy(studentServiceImpl);

        proxy.addStudent();
        proxy.updateStudent();
    }
    
}

5、总结

由上面的例子可以知道,真正的业务实现类是没有日志的功能的。日志的功能是在代理类里面增加的。且调用层也没有真正的去调用真正的业务实现类,而是调用了它的代理类来。可以看出,代理模式的主要的特点就是不修改原有的业务逻辑而可以对业务类进行其他功能的增加。如:日志的输出、参数的校验、根据返回的结果做相应的处理......

二、动态代理

聪明的小明发现了静态代理模式的一些缺点:

  • 当接口增加或者删除一些接口时,代理类也要同时的进行增加或者修改,不易维护;
  • 如果一个接口对应的创建一个代理类时,代理类就会过多,也不方便维护;
  • 如果所有的接口都共用一个代理类,则这个代理类就会显得过于庞大;

那有什么更好的方法来优化呢?有,就是今天的主角:动态代理

最常用的动态代理的方法有两种:

1、通过实现接口的方式——JDK动态代理

JDK动态代理主要涉及两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler。

针对上面的静态代理,我们如何使用动态代理来进行增加日志的功能呢?

  • 创建一个代理类LogHandler,这个代理类实现了接口InvocationHandler(静态代理类是实现了其他具体的业务接口),在声明一个幕后的执行者(实际的业务处理类)Object,没错,就是Object,万物皆Object。

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    //实现InvocationHandler接口
    public class LogHandler implements InvocationHandler {
        //实际的业务执行者
        private Object target;
        //构造注入
        public LogHandler(Object obj) {
            this.target = obj;
        }
        //实现 InvocationHandler接口中的方法
        //proxy:代表动态代理对象
        //method:代表正在执行的方法
        //args:代表调用目标方法时传入的实参
    	@Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
            beforeLog(args);
            //真正的执行者执行业务逻辑,返回结果
            Object result = method.invoke(target, args); 
            afterLog();
        }
        
        //假设下面是一些日志的方法(代理类增加的一些功能)
        private void beforeLog(Object[] args) {
            if (args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    System.out.println("请求的参数:" + args[i].toString());
                }
            }
            System.out.println("请求开始的时间:" + new Date());
        }
    
        private void afterLog() {
            System.out.println("请求结束的时间:" + new Date());
        }
    }
    
  • 调用

    public class StudentController {
        
        public static void main(String[] args) {
            //要求一个真正的执行者
            StudentService studentService = new StudentServiceImpl();
            //创建一个InvocationHandler,并关联了真正的业务执行者studentService
            InvocationHandler logHandler = new LogHandler(studentService);
            
            //获取具体业务实现类的Class
            Class<?> studentServiceClass = studentService.getClass();
            
            //创建一个动态的代理类
            //参数1:loader:具体业务实现类的类加载器
            //参数2:interface[]:具体业务实现类的接口,是一个数组,代理类会动态的实现这些接口后,即可对外调用。
            //参数3:InvocationHandler 表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用
            StudentService studentServicerProxy = (StudentService) Proxy.newProxyInstance(studentServiceClass.getClassLoader(), studentServiceClass.getInterfaces(), logHandler);
            Student s = new Student();
            
            //通过动态代理类来调用新增、修改学生的方法
            studentServicerProxy.addStudent(s);
            studentServicerProxy.updateStudent(s);
        }
        
    }
    

image

2、通过继承类的方式——CGLIB动态代理

JDK实现动态代理是通过接口定义业务方法,对于一些没有接口的类,如果要实现动态代理,则需要到CGLIB了。其采用了底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有的父类方法的调用,织入横切。但是因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLIB动态代理均是实现Spring AOP的基础。CGlib不是java自带的API,所以要引入cglib的jar包。

  • 创建一个类,其没有接口的实现

    public class StudentServiceImpl {
        
        public int addStudent(Student student) {
            // TODO 在这里实现了把学生的信息增加到数据库中
            System.out.printLn("在这里实现了把学生的信息增加到数据库中");
            // 伪代码
            int retult = studentRepository.insertStudent(student);
        }
        
        public int updateStudent(Student student) {
            // TODO 在这里实现了把学生的信息更新到数据库中
            System.out.printLn("在这里实现了把学生的信息更新到数据库中");
            // 伪代码
            int retult = studentRepository.updateStudent(student);
        }
    }
    
  • 编写一个LogInterceptor类,继承MethodInterceptor

    public class LogInterceptor implements MethodInterceptor {
     	/**
         * @param object 表示要进行增强的对象
         * @param method 表示拦截的方法
         * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
         * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
         * @return 执行结果
         * @throws Throwable
         */
        @Override
        public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            beforeLog(objects);
            //注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
            Object result = methodProxy.invokeSuper(object, objects);   
            afterLog();
            return result;
        }
        //假设下面是一些日志的方法(代理类增加的一些功能)
        private void beforeLog(Object[] args) {
            if (args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    System.out.println("请求的参数:" + args[i].toString());
                }
            }
            System.out.println("请求开始的时间:" + new Date());
        }
    
        private void afterLog() {
            System.out.println("请求结束的时间:" + new Date());
        } 
    }
    
  • 调用

    class LogInterceptorController {
    
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(StudentServiceImpl.class);
            enhancer.setCallback(new LogInterceptor());
            //创建代理类
            StudentServiceImpl stImpl = (StudentServiceImpl) enhancer.create();
            Student s = new Student();
            s.setStName("张三");
            stImpl.updateStudent(s);
        }
    }
    
  • 执行结果

image

可以看到,这个是和JDK动态代理实现的效果是一样的

  • 此外,还可以进一步多个 MethodInterceptor 进行过滤筛选,我们再创建一个LogInterceptor1,在日志的输入上做区分。

    public class LogInterceptor1 implements MethodInterceptor {
    
        @Override
        public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            beforeLog(objects);
            //注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,
            // method.invoke执行的是子类的方法
            Object result = methodProxy.invokeSuper(object, objects);
            afterLog();
            return result;
        }
    
        //假设下面是一些日志的方法(代理类增加的一些功能)
        private void beforeLog(Object[] args) {
            if (args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    System.out.println("请求的参数1:" + args[i].toString());
                }
            }
            System.out.println("请求开始的时间1:" + new Date());
        }
    
        private void afterLog() {
            System.out.println("请求结束的时间1:" + new Date());
        }
    }
    
  • 再创建一个选择器

    public class StudnetCallbackFilter implements CallbackFilter {
    
        @Override
        public int accept(Method method) {
            //新增,使用LogInterceptor代理
            if ("addStudent".equals(method.getName())) {
                return 0;
            }
            //updateStudent,使用LogInterceptor1代理
            if ("updateStudent".equals(method.getName())) {
                return 1;
            }
            return 1;
        }
    }
    
  • 再进行测试

    class LogInterceptorController {
    
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(StudentServiceImpl.class);
    //        enhancer.setCallback(new LogInterceptor());
            enhancer.setCallbacks(new Callback[]{new LogInterceptor(), new LogInterceptor1(), NoOp.INSTANCE});   // 设置多个拦截器,NoOp.INSTANCE是一个空拦截器,不做任何处理
            enhancer.setCallbackFilter(new StudnetCallbackFilter());
            //创建代理类
            StudentServiceImpl stImpl = (StudentServiceImpl) enhancer.create();
            Student s = new Student();
            s.setStName("张三");
            stImpl.addStudent(s);
            System.out.println("================================");
            stImpl.updateStudent(s);
        }
    }
    

image

总结

静态代理

​ 不同的接口要有不同的代理类实现,会很冗余;

JDK动态代理

​ 解决了静态代理中冗余的代理实现类问题,JDK 动态代理是基于接口设计实现的,如果被代理对象没有实现接口,会抛出异常。

CGLIB动态代理

​ 没有接口也能实现动态代理,而且采用字节码增强技术。技术实现相对难理解些