前言
代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了。只需要在代理类上增加就可以了。
其实代理模式的核心思想就是这么简单,在java中,代理又分静态代理和动态代理2种,其中动态代理根据不同实现又区分基于接口的的动态代理和基于子类的动态代理。
其中静态代理由于比较简单,面试中也没啥问的,在代理模式一块,问的最多就是动态代理,而且动态代理也是spring aop的核心思想,spring其他很多功能也是通过动态代理来实现的,比如拦截器,事务控制等。
熟练掌握动态代理技术,能让你业务代码更加精简而优雅。如果你需要写一些中间件的话,那动态代理技术更是必不可少的技能包。
静态代理
静态代理,就是通过声明一个明确的代理类来访问源对象。
我们有1个接口,Person。这个个接口各有2个实现类,UML如下图:
实现
接口:person.java
package StaticProxy;
/**
* @author zyz
* @version 1.0
* @data 2023/2/15 13:29
* @Description:
*/
public interface Person {
/**
* 起床
*/
public void wakeup();
/**
* 睡觉
*/
public void sleep();
}
实现类:Student .java
package StaticProxy;
/**
* @author zyz
* @version 1.0
* @data 2023/2/15 13:32
* @Description:
*/
public class Student implements Person{
private String name;
public Student(){
}
public Student(String name){
this.name = name;
}
@Override
public void wakeup() {
System.out.println("学生:"+name+",起床了!!!");
}
@Override
public void sleep() {
System.out.println("学生:"+name+",睡觉了!!!");
}
}
假设我们现在要做一件事,就是在所有的实现类调用wakeup()前增加一行输出早安,调用sleep()前增加一行输出晚安。那我们只需要编写1个代理类PersonProxy
代理类:PersonProxy .java
package StaticProxy;
/**
* @author zyz
* @version 1.0
* @data 2023/2/15 13:35
* @Description:
*/
public class PersonProxy implements Person{
private Person person;
public PersonProxy(Person person){
this.person = person;
}
@Override
public void wakeup() {
System.out.println("早上好啊!!!");
person.wakeup();
}
@Override
public void sleep() {
System.out.println("晚上好啊!!!");
person.sleep();
}
}
测试类
package StaticProxy;
/**
* @author zyz
* @version 1.0
* @data 2023/2/15 13:37
* @Description:
*/
public class Test {
public static void main(String[] args) {
Person student1 = new Student("张三");
PersonProxy studentProxy = new PersonProxy(student1);
studentProxy.wakeup();
studentProxy.sleep();
}
}
结果
结论:
静态代理的代码相信已经不用多说了,代码非常简单易懂。这里用了1个代理类,代理了Person接口。
这种模式虽然好理解,但是缺点也很明显:
- 会存在大量的冗余的代理类,这里演示了1个接口,如果有10个接口,就必须定义10个代理类。
- 不易维护,一旦接口更改,代理类和目标类都需要更改。
动态代理
动态代理,通俗点说就是:无需声明式的创建java代理类,而是在运行过程中生成"虚拟"的代理类,被ClassLoader加载。从而避免了静态代理那样需要声明大量的代理类。
JDK从1.3版本就开始支持动态代理类的创建。主要核心类只有2个:java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
。
还是前面那个例子,用JDK动态代理类去实现的代码如下:
创建一个JdkProxy类,用于统一代理:
package DynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author zyz
* @version 1.0
* @data 2023/2/15 13:28
* @Description:
*/
public class JdkProxy implements InvocationHandler {
private Object bean;
public JdkProxy(Object bean) {
this.bean = bean;
}
/**
* 其中proxy为代理过之后的对象(并不是原对象),method为被代理的方法,args为方法的参数。
*
* 如果你不传原有的bean,直接用method.invoke(proxy, args)的话,那么就会陷入一个死循环。
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("wakeup")) {
System.out.println("早安~~~");
} else if (methodName.equals("sleep")) {
System.out.println("晚安~~~");
}
return method.invoke(bean, args);
}
}
测试
package DynamicProxy;
import StaticProxy.Person;
import StaticProxy.Student;
import java.lang.reflect.Proxy;
/**
* @author zyz
* @version 1.0
* @data 2023/2/15 13:46
* @Description:
*/
public class Test {
public static void main(String[] args) {
JdkProxy proxy = new JdkProxy(new Student("李四"));
Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
student.wakeup();
student.sleep();
}
}
结果
可以看到,相对于静态代理类来说,无论有多少接口,这里只需要一个代理类。核心代码也很简单。唯一需要注意的点有以下2点:
JDK动态代理是需要声明接口的,创建一个动态代理类必须得给这个”虚拟“的类一个接口。可以看到,这时候经动态代理类创造之后的每个bean已经不是原来那个对象了。
这里JdkProxy最核心的方法就是
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
其中proxy为代理过之后的对象(并不是原对象),method为被代理的方法,args为方法的参数。
如果你不传原有的bean,直接用method.invoke(proxy, args)的话,那么就会陷入一个死循环。