Java的代理模式

java有三种代理模式

静态代理

jdk动态代理

cglib实现动态代理

代理模式的定义: 为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

java的三种代理模式优缺点

|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | 静态代理 | jdk动态代理 | cglib实现动态代理 |
| | 简单易懂:静态代理模式的实现相对简单,易于理解和使用。代理类在编译时就已经存在,开发人员可以直接通过编码方式创建和配置代理对象。 可以在代理类中添加额外的逻辑:通过静态代理,我们可以在代理类中添加额外的逻辑,如安全检查、日志记录、性能监控等,而无需修改被代理类的代码。这种方式使得代理类具有更高的灵活性和扩展性。 控制访问权限:静态代理可以控制对被代理对象的访问权限。代理类可以添加一些条件或限制,以决定是否调用被代理类的方法,从而实现对被代理对象的权限控制。 低耦合:通过静态代理,被代理类和代理类是相互独立的,彼此并无直接的依赖关系。这种低耦合的设计能够降低系统的复杂性,提高代码的可维护性和可测试性。 可以对多个对象进行代理:静态代理模式可以对多个对象进行代理,这样我们可以在不改变原始对象的情况下,通过代理对象来进行统一的管理和控制。 | 简化代理模式实现:相比于静态代理,JDK动态代理通过反射机制实现,无需手动编写大量的代理类代码。只需要编写一个代理处理器即可,大大简化了代理模式的实现过程。 可以代理任意接口:JDK动态代理是基于接口的代理,可以代理任意实现了接口的类。这意味着无论被代理类的具体实现如何,只要实现了接口,就可以使用JDK动态代理进行代理操作。 代码结构清晰:使用JDK动态代理,代理类和被代理类之间的关系更加清晰。代理类实现了InvocationHandler接口,可以在方法调用前后添加自定义逻辑,而被代理类则专注于核心业务逻辑的实现。这样有利于代码的维护和扩展。 可以动态改变代理行为:由于代理类是在运行时动态生成的,可以在代理类中灵活地修改和调整代理行为。可以根据实际需要,在不修改被代理类代码的情况下,通过修改代理处理器的逻辑,实现不同的代理行为。 高性能:相比于CGlib等其他动态代理实现,JDK动态代理在性能上具有较好的表现。JDK动态代理使用Java原生的反射机制,在执行方法调用时会有一些性能损耗,但仍然比一些其他动态代理实现方式更高效。 | 无需接口:CGLib可以为非接口类生成代理,这意味着我们可以代理那些没有实现任何接口的类。相比于JDK动态代理需要目标类实现接口的限制,CGLib的使用更加灵活。 高性能:CGLib采用了底层的字节码操作技术,通过生成目标类的子类,并重写其中的方法来实现代理。相比于JDK动态代理的反射调用,CGLib的代理调用更快速,性能更高效。 简化代理:CGLib可以在运行时生成代理类,避免了编写很多重复的代理类代码的麻烦。它通过继承来实现代理,这样我们就可以专注于业务逻辑的实现,而无需关心代理类的创建和管理。 支持回调过滤:CGLib提供了MethodInterceptor接口,可以在代理类的方法调用前后插入自定义的逻辑。这允许我们在目标方法执行前后进行一些额外的处理,如日志记录、权限校验、缓存等。 可扩展性强:CGLib是一个功能强大的库,除了可以实现动态代理外,还可以用于AOP编程、实现Bean的动态注入等。它提供了丰富的API和扩展点,可以根据实际需求进行灵活的扩展和定制。 |
| | 代码复杂度增加:使用静态代理会引入额外的代理类,从而增加代码的复杂性。每个被代理类都需要对应一个代理类,随着被代理类的增加,代理类的数量也会增加,导致代码变得冗长且难以维护。 频繁更新代理类:如果被代理类的接口发生变化,代理类也需要相应地进行修改。这意味着在使用静态代理时,每次接口发生变动,需要手动更新相关的代理类,增加了维护成本和风险。 静态代理不支持动态代理:静态代理是在编译时期生成的代理类,对于动态生成的对象,无法进行代理。如果需要对一批动态生成的对象进行代理,静态代理就显得无能为力。 单一职责原则破坏:静态代理中代理类与被代理类紧密耦合,代理类不仅要实现接口方法的委托,还要添加额外的逻辑。这样可能导致代理类承担了过多的责任,违反了单一职责原则。 动态扩展困难:静态代理在代理类定义时就已经确定了被代理对象,如果需要代理新的类或者对象,就要重新编写对应的代理类。这种静态的特性使得动态扩展变得困难,难以应对频繁变化的需求。 | 只能代理接口:JDK动态代理只能代理实现了接口的类,对于没有实现接口的类,无法进行代理。这一限制使得某些类无法使用JDK动态代理,需要考虑其他代理实现方式。 性能有一定损耗:JDK动态代理使用反射机制,在方法调用时会涉及到反射操作,这会引入一定的性能损耗。相比起静态代理,JDK动态代理的性能稍差一些。对于对性能要求较高的场景,可能需要考虑其他更高效的代理实现方式。 无法代理私有方法:JDK动态代理只能代理公开的方法,无法代理私有方法。这是由于代理机制是基于接口生成的动态代理类,无法直接访问类的私有成员。 需要实现InvocationHandler接口:使用JDK动态代理,需要编写一个实现InvocationHandler接口的代理处理器类。虽然这使得动态代理实现更加灵活,但同时也增加了开发的复杂性。 无法直接修改被代理类:JDK动态代理是通过代理接口生成代理类,而不是直接修改被代理类的字节码。这一特性使得在不修改被代理类的情况下无法对其进行修改或添加新的行为。 | 类加载器限制:CGLib使用了底层的字节码操作技术来生成代理类,这需要在运行时动态创建和加载类。某些情况下,类加载器限制可能会导致无法使用CGLib动态代理,例如在一些受限的环境下或者使用特定的类加载器。 需要依赖第三方库:CGLib是一个第三方库,不是Java的标准库。使用CGLib时需要将其引入项目中并进行相应的配置,增加了项目的依赖。 Proxy无法代理final方法:CGLib的动态代理无法代理final方法,因为final方法无法被子类重写。如果目标类中存在final方法,CGLib将无法为其生成代理,这在一些特定场景下可能会有限制。 难以调试:使用CGLib生成的代理类是动态生成的字节码,对于开发者来说可读性较差。对于调试代理类的问题或者理解代理逻辑,可能需要额外的工具和技巧。 内存占用较高:由于CGLib是通过生成代理类的子类来实现的,每次代理都会创建一个新的类,并加载到JVM中。如果频繁地创建代理对象,可能会产生大量的代理类,增加内存占用。 |

静态代理

先创建一个父类或者接口,被代理对象和代理对象都应该继承父类或者实现接口。

创建接口Movie.java

java 复制代码
public interface Movie {
    void play();
}

创建实现类RealMovie.java

java 复制代码
public class RealMovie implements Movie {
    @Override
    public void play() {
        System.out.println("播放金刚大战哥斯拉");
    }
}

创建代理类ProxyMovie.java

java 复制代码
public class ProxyMovie implements Movie {
    Movie movie;

    public ProxyMovie(Movie movie) {
        this.movie = movie;
    }

    @Override
    public void play() {
        System.out.println("电影还没开始,买点爆米花");
        movie.play();
        System.out.println("电影结束了,买个哥斯拉模型");
    }
}

创建测试类Test.java

java 复制代码
public class Test {
    public static void main(String[] args) {
        RealMovie movie = new RealMovie();
        ProxyMovie proxyMovie = new ProxyMovie(movie);
        proxyMovie.play();
    }
}

总结:在不改变RealMovie的前提下,使用ProxyMovie对其进行了增强。但是由于目标对象和代理对象都实现了同一个接口,一旦这个接口添加或者删除方法,那么代理对象和目标对象都要进行相应的操作,耦合度太高。

静态代理的应用:在使用实现Runnable接口实现多线程的时候,将Runnable接口的实现类的对象作为Thread的构造函数的参数

jdk动态代理

为了解决静态代理的代理类必需实现接口的所有方法的问题,动态代理诞生了,java的动态代理有如下的特点:

1、代理对象不需要跟目标对象实现同一接口或继承同一个父类(解决了静态代理的痛点);

2、代理对象的生成利用jdk提供的api,在JVM内存中动态的构建代理对象。

创建接口Movie.java

java 复制代码
public interface Movie {
    void play();
}

创建实现类RealMovie.java

java 复制代码
public class RealMovie implements Movie{
    @Override
    public void play() {
        System.out.println("播放侏罗纪世界");
    }
}

创建代理类ProxyMovie.java

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ProxyMovie implements InvocationHandler {
    private Object movie;

    public ProxyMovie(Object movie) {
        this.movie = movie;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("电影还没开始,买瓶可乐");
        method.invoke(movie, args);
        System.out.println("电影还结束,买一只恐龙模型");
        return null;
    }
}

创建测试类Test.java

java 复制代码
public class Test {
    public static void main(String[] args) {
        RealMovie realMovie = new RealMovie();
        InvocationHandler proxyMovie = new ProxyMovie(realMovie);
        // 创建代理对象
        Movie movie = (Movie)Proxy.newProxyInstance(realMovie.getClass().getClassLoader(), realMovie.getClass().getInterfaces(), proxyMovie);
        System.out.println(movie.getClass().getSimpleName());
        movie.play();
    }
}

总结:在这里,代理类没有实现Movie接口,而是实现了InvocationHandler接口,然后重写了invoke方法,与Movie接口的耦合度降低。

cglib实现动态代理

在使用jdk动态代理时,被代理对象是实现了一个接口的,假如实际中被代理对象没有实现接口该怎么办呢,cglib动态代理便应运而生,原理是创建一个目标对象的子类对象,然后进行增强操作,所以被代理对象的类不能是final类型的(无法被继承,也就没有子类),被调用的方法也不能是final和static类型的(不会执行代理功能)。

在pom.xml文件中添加cglib依赖

java 复制代码
<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>3.3.0</version>
</dependency>

创建CglibService.java

java 复制代码
public class CglibService {
    public void movie() {
        System.out.println("播放侏罗纪公园");
    }
}

创建CglibServiceProxy.java类

java 复制代码
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibServiceProxy implements MethodInterceptor {
    private Object object;

    public CglibServiceProxy(Object object) {
        this.object = object;
    }

    public Object getProxyInstance() {
        //创建代理类
        Enhancer enhancer = new Enhancer();
        //继承要被代理的类
        enhancer.setSuperclass(object.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("电影即将开始,大家有序进入影厅");
        Object obj = method.invoke(object);
        System.out.println("电影结束,大家离开电影院");
        return obj;
    }
}

创建CglibProxyTest.java

java 复制代码
public class CglibProxyTest {
    public static void main(String[] args) {
        CglibService service = new CglibService();
        CglibServiceProxy proxy = new CglibServiceProxy(service);
        CglibService proxyInstance = (CglibService)proxy.getProxyInstance();
        proxyInstance.movie();
    }
}

总结:由以上可以看出,使用cglib代理可以代理一些没有父类的目标对象,所以以后如果要代理没有实现接口的类,可以选择使用cglib代理。

相关推荐
考虑考虑2 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261352 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊3 小时前
Java学习第22天 - 云原生与容器化
java
渣哥5 小时前
原来 Java 里线程安全集合有这么多种
java
间彧5 小时前
Spring Boot集成Spring Security完整指南
java
间彧5 小时前
Spring Secutiy基本原理及工作流程
java
Java水解6 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆9 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学9 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole9 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端