代理设计模式介绍
简单概括:在不违反开闭原则的情况下增强对象
代理模式是一种结构型设计模式,它允许对象提供对其它对象的访问,以控制对这些对象的访问方式。代理对象充当原始对象的替代品,它们可以在不改变原始对象代码的情况下提供额外的功能,例如:访问控制,远程访问,Spring配置类中Full模式(返回存储的对象而不执行new对象),连接池的连接对象(关闭连接修改为返回连接池)等。
原理图
需要注意的有下面几点:
- 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是
Subject
。 - 接口真正实现者是上图的
RealSubject
,但是它不与用户直接接触,而是通过代理。 - 代理就是上图中的
Proxy
,由于它实现了Subject
接口,所以它能够直接与用户接触。 - 用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。
代理模式通常分为静态代理和动态代理两种
区别是:静态是在编译时确认而动态是优先时确认
静态代理
静态代理是指代理类在编译期间就已经确定,并在程序运行前被编译成字节码文件。
代理类需要实现与目标类相同的接口,实现目标类中所有的方法并在代理类中调用目标类相应的方法。
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和实现类(被代理类)应该共同实现一个接口,或者是共同继承某个类。实现类中的所有的方法只能通过代理类调用。
优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
缺点
- 代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护。
静态代理案例
public class Test {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
// 创建时调用构造方法传入realSubject对象
Subject proxySubject = new ProxySubject(realSubject);
// 通过调用doSomething方法在ProxySubject对象的内部调用RealSubject类的doSomething方法
proxySubject.doSomething();
}
}
// 接口
interface Subject {
void doSomething();
}
// 具体实现类
class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("《流浪地球2》");
}
}
// 代理类
class ProxySubject implements Subject {
private Subject subject;
public ProxySubject(Subject subject) {
this.subject = subject;
}
@Override
public void doSomething() {
System.out.println("电影广告");
//调用具体实现类的方法
subject.doSomething();
System.out.println("电影结束");
}
}
比如在多线程中的运用:
在Java中,当我们想要启动一个新的线程时,我们需要创建一个Thread对象,并调用其start方法来启动线程。实际上,在Thread类的内部,start方法是通过JNI调用底层操作系统提供的线程实现来启动一个新的线程。
在这个过程中,Thread类可以被看作是用户与底层线程实现之间的代理。用户通过Thread类来创建和控制线程,而底层线程实现则负责实际的线程执行过程。这种代理模式就是静态代理模式。
具体来说,Thread类提供了一系列控制线程执行的方法,如sleep、join等方法,使得用户可以方便地控制线程的执行过程。而底层线程实现则负责实际的线程执行过程,包括线程的创建、销毁、调度等过程。
在调用Thread类的start方法时,实际上是在使用静态代理模式,将用户提交的任务委托给底层线程实现,并启动一个新的线程来执行任务。这样,通过代理的方式,将线程的创建与销毁过程与线程的执行过程分离开来,使得线程的实现更加灵活、可控,更加符合用户的需求。
动态代理
动态代理是指代理类在运行时通过反射机制动态创建,无需在编译期间确定。
动态代理可以代理任意实现了接口的类,代理类会实现与目标类相同的接口,并在代理类中创建一个InvocationHandler对象,每次对目标类的方法调用都会通过InvocationHandler进行处理。
用途
当不方便修改具体业务(实现)类(代码)时,又需要进行功能的附加与增强。
禁止客户端直接调用具体业务(实现)类导致不安全的情况(就像将实体类的属性定义为私有一样)
JDK 动态代理
JDK动态代理是Java SDK提供的一种动态代理实现方式。它要求被代理的对象必须实现至少一个接口,并且使用Proxy类创建代理对象。在创建代理对象时,需要实现InvocationHandler接口,该接口包含一个invoke方法,在代理对象调用方法时被调用。JDK动态代理的优点是它不需要第三方库的支持,而且代理对象是基于接口实现的,更加符合面向对象的设计原则。但是,JDK动态代理只能代理实现了接口的类,无法代理没有实现接口的类。
注意:InvocationHandler 是一个函数式接口,所以可以使用 lambda 表达式进行简化
案例1
public class Test{
public static void main(String[] args) {
BigStar bigStar = new BigStar("张学友");
Star starProxy = ProxyUtil.createProxy(bigStar);
starProxy.sing("吻别");
starProxy.dance();
}
}
//代理工具类
class ProxyUtil{
// createProxy方法使用Proxy.newProxyInstance方法创建并返回一个代理对象,
// 该代理对象实现了Star接口,并在invoke方法中调用了BigStar对象的方法。
// 这样,当代理对象调用sing或dance方法时,实际上是调用了BigStar对象的对应方法,
// 并在前后加上了一些额外的处理。
public static Star createProxy(BigStar bigStar){
// newProxyInstance参数介绍
// ClassLoader loader 参数1:代理类的类加载器
// Class<?>[] interfaces 参数2:代理对象要实现的接口
// InvocationHandler h 参数3:用于指定生成的代理对象要干什么事情
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class}, new InvocationHandler() {
@Override//回调方法
//proxy:代理对象 method:需要调用的方法 args:调用时需要传入的参数数组
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//invoke:决定代理对象干什么事,在这写代码
System.out.println("代理人正在处理" + method.getName() + "方法");
//通过反射调用bigStar的需要调用的方法
Object result = method.invoke(bigStar, args);
System.out.println("代理人处理完毕");
//返回结果
return result;
}
});
return starProxy;
}
}
//需要代理的类
class BigStar implements Star{
private String name;
public BigStar(String name){
this.name = name;
}
@Override
public String sing(String name) {
System.out.println(this.name + "正在唱" + name);
return "不是null";
}
@Override
public void dance() {
System.out.println(this.name + "正在优美的跳舞~~");
}
}
//接口
interface Star{
String sing(String name);
void dance();
}
案例2:对连接池的连接对象的代理
回调处理器
需要继承 InvocationHandler
负责调用被代理类的所有方法,进行一些增强的逻辑
public class ConnectionInvocationHandler implements InvocationHandler {
// 目标对象(被代理对象)
private Object object;
/**
* 通过构造方法传入目标对象
*
* @param object
*/
private LinkedList<Connection> pool;
public ConnectionInvocationHandler(Object object, LinkedList<Connection> pool) {
this.object = object;
this.pool = pool;
}
/**
* 核心的回调方法,目的是负责调用目标对象的行为
* 这样可以在调用目标方法前后额外执行一些增强的逻辑
*
* 动态代理对象去调用
*
* @param proxy 由 jdk 动态创建出来的代理对象
* @param method 目标对象的具体方法
* @param args 目标对象的参数(动态数组)
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("close".equals(method.getName())) {
pool.addLast((Connection) proxy);
return null;
} else {
return method.invoke(object, args);
}
}
}
连接池
public class ConnectionPool {
/**
* 连接池(存放连接的集合)
* 链表(前面拿后面放)
*/
private LinkedList<Connection> pool = new LinkedList<>();
private String url;
private String userName;
private String password;
public ConnectionPool(String url, String userName, String password, int size) {
this.url = url;
this.userName = userName;
this.password = password;
Connections(size);
}
/**
* 根据size添加数据到连接池中
*/
private void Connections(int size) {
try {
for (int i = 0; i < size; i++) {
// 从数据库获取连接对象
Connection connection = DriverManager.getConnection(url, userName, password);
// 将连接对象返给池中
pool.add(createProxy(connection));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 给连接对象套代理
*
* @param connection
* @return
*/
private Connection createProxy(Connection connection) {
/**
* 通过 JDK 提供的 Proxy 类来动态创建代理对象
* newProxyInstance 方法需要提供三个参数
* 参数1:需要提供一类加载器去加载动态创建出来的代理字节码,从而实例化一个代理对象
* 参数2:目标对象属性的所有接口的 Class,因为 JDK 动态代理是一定要根据接口来创建一个代理对象,创建出来的这个代理对象会自动实现这些接口
* 参数3:自定义的回调处理器
*
* 为什么需要类加载器:
* 类加载器的存在是 class(自解码文件) -> byte[](字节数组) -> JVM(创建成 class对象)
* 类加载器的目的是为了加载字节数组到 class对象 交给 JVM 创建实例
* 而 JDK动态代理 是通过类加载器 创建 实例 并实现被代理对象的所有接口
*
* 总结:目标对象一定要实现接口,否则无法创建代理对象,因为JDK动态代理是基于接口来生成代理对象
*/
return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{Connection.class}, new ConnectionInvocationHandler(connection, pool));
}
/**
* 从池里面获取代理连接
*
* @return
*/
public Connection getConnection() {
if (size() <= 0) {
Connections(5);
}
return pool.removeFirst();
}
/**
* 查看连接池的大小
*
* @return
*/
public int size() {
return pool.size();
}
}
CGLIB 动态代理
CGLIB动态代理是通过字节码技术动态生成代理类的方式实现的。它不要求被代理的对象实现接口,因此可以代理没有实现接口的类。使用CGLIB动态代理时,需要引入cglib库,并使用Enhancer类创建代理对象。在创建代理对象时,需要实现 MethodInterceptor 接口,该接口包含一个intercept方法,在代理对象调用方法时被调用。CGLIB动态代理的缺点是它需要引入第三方库,并且代理对象是基于继承实现的,可能会破坏类的层次结构,不符合面向对象的设计原则。
注意:MethodInterceptor 是一个函数式接口,所以可以使用 lambda 表达式进行简化
maven依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib-nodep -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
</dependency>
案例1
public class Test {
public static void main(String[] args) {
// 创建目标对象
UserDao target = new UserDao();
// 获取增强对象
Enhancer enhancer = new Enhancer();
// 用于设置enhancer对象的父类
enhancer.setSuperclass(UserDao.class);
// 设置enhancer对象的回调函数
// 使用了MethodInterceptor接口的一个实现类,重写了intercept()方法,
// 实现了代理对象的具体逻辑,包括开启事务、调用目标对象的方法、提交事务等。
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事务");
// 执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务");
return returnValue;
}
});
// 创建代理对象(代理类是被代理类的子类)
UserDao proxy = (UserDao) enhancer.create();
// 调用代理对象方法
proxy.save();
}
}
/**
* 目标对象
*/
class UserDao {
public void save() {
System.out.println("保存用户");
}
}
案例2:对连接池的连接对象的代理
public class ConnectionPool {
/**
* 连接池(存放连接的集合)
* <p>
* 链表(前面拿后面放)
*/
private LinkedList<Connection> pool = new LinkedList<>();
private String url;
private String userName;
private String password;
private Integer size;
public void init() {
connections();
}
/**
* 根据size添加数据到连接池中
*/
private void connections() {
try {
for (int i = 0; i < size; i++) {
// 从数据库获取连接对象
Connection connection = DriverManager.getConnection(url, userName, password);
// 将连接对象返给池中
pool.add(createProxy(connection));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 给连接对象套代理
*
* @param connection
* @return
*/
private Connection createProxy(Connection connection) {
// 创建代理生成器
Enhancer enhancer = new Enhancer();
// 要告诉代理生成器需要登录的父类
enhancer.setSuperclass(Connection.class);
/**
* 设置方法拦截器(设置回调方法)
*
* proxy:代理对象
* method:父类方法
* args:参数
* methodProxy:子类方法
*
* 注意:调用时,调用子类的代理方法不要调用父类方法(method)
* 补充:可以使用methodProxy+代理对象调用原始方法
*/
enhancer.setCallback((MethodInterceptor) (proxy, method, args, methodProxy) -> {
if ("close".equals(method.getName())) {
pool.addLast((Connection) proxy);
return null;
} else {
return methodProxy.invoke(connection, args);
}
});
// 创建代理对象(运行时动态创建的子类对象就是代理对象)
return (Connection) enhancer.create();
}
/**
* 从池里面获取代理连接
*
* @return
*/
public Connection getConnection() {
if (size() <= 0) {
connections();
}
return pool.removeFirst();
}
/**
* 查看连接池的大小
*
* @return
*/
public int size() {
return pool.size();
}
}
jdk 和 cglib 区别
比喻:
jdk:当兄弟
cglib:当儿子
- 代理对象类型不同
JDK动态代理要求被代理对象实现至少一个接口,代理对象是基于接口实现的。而CGLIB动态代理不要求被代理对象实现接口,代理对象是基于继承实现的。
- 代理对象生成方式不同
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象。
CGLIB动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
JDK动态代理是通过反射机制实现的,它在运行时生成代理对象,代理对象是由JVM动态生成的。而CGLIB动态代理是通过字节码生成技术实现的,它在运行时生成代理对象的子类,代理对象是由CGLIB库在运行时动态生成的。
- 性能表现不同
JDK动态代理使用的是Java原生的反射机制,在生成代理对象时需要消耗一定的时间和内存,因此它的性能比CGLIB动态代理略低。CGLIB动态代理使用的是字节码生成技术,虽然在生成代理对象时可能需要消耗更多的时间和内存,但一旦生成了代理对象,它的性能比JDK动态代理更高。
总之,JDK动态代理适用于代理接口类型的对象,CGLIB动态代理适用于代理普通Java类的对象。在实际应用中,需要根据具体的场景和需求来选择合适的动态代理实现方式。