首页 > 其他分享 >Spring之代理模式

Spring之代理模式

时间:2024-04-09 11:31:12浏览次数:14  
标签:IService Spring void System 模式 代理 方法 public

目录

前言

为什么需要代理呢?  

JDK动态代理

方法一:

方法二:

 通用代理对象实现

 CGLIB代理

1.什么是CGLIB呢?

2.与JDK代理有什么区别呢?

3.项目建立

4.CGLIB常用API


前言

      在Spring框架中,代理是一种用于实现AOP(Aspect-Oriented Programming,面向切面编程)和声明式事务管理的技术。Spring框架通过代理机制在运行时动态地创建代理对象,来实现对目标对象的横切关注点的管理。学习代理前可以先学习一下AOP的知识点:

                                                                                                                 http://t.csdnimg.cn/akGyU                                                                 

为什么需要代理呢?  

            与其说为什么需要代理,还不如说,代理实际上有什么用,到底是什么!

从代码上入手:

第一步:假设我们有一个接口 IService :

public interface IService {
    void m1();
    void m2();
    void m3();
}

第二步,我们有两个实现该接口的类,分别为ServiceA 和ServiceB:

public class ServiceA implements IService {
    @Override
    public void m1() {
        System.out.println("我是ServiceA中的m1方法!");
    }
 
    @Override
    public void m2() {
        System.out.println("我是ServiceA中的m2方法!");
    }
 
    @Override
    public void m3() {
        System.out.println("我是ServiceA中的m3方法!");
    }
}
public class ServiceB implements IService {
    @Override
    public void m1() {
        System.out.println("我是ServiceB中的m1方法!");
    }
 
    @Override
    public void m2() {
        System.out.println("我是ServiceB中的m2方法!");
    }
 
    @Override
    public void m3() {
        System.out.println("我是ServiceB中的m3方法!");
    }
}

此时此刻,我们可以使用多态的特性,实例化时,对应需要的实体类,并且调用方法!

测试代码:

import org.junit.Test;
 
public class ProxyTest {
    @Test
    public void m1() {
        IService serviceA = new ServiceA();
        IService serviceB = new ServiceB();
        serviceA.m1();
        serviceA.m2();
        serviceA.m3();
 
        serviceB.m1();
        serviceB.m2();
        serviceB.m3();
    }
}

测试结果:

假设,如果你的领导要找你麻烦,执行方法时,需要记录运行的时间!那么我们该怎么办呢?

蠢方法:修改上述每一个方法,虽然有用,但是也太不靠谱了。

因此我们需要一个代理类!ServiceProxy类 使用该代理类实现接口,并且访问Iservice的实现类!

// IService的代理类
public class ServiceProxy implements IService {
    //目标对象,被代理的对象
    private IService target;
 
    public ServiceProxy(IService target) {
        this.target = target;
    }
 
    @Override
    public void m1() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
    }
 
    @Override
    public void m2() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
    }
 
    @Override
    public void m3() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
    }
}

在代理类的方法中,我们添加额外的功能,或许你还没有明白该代码的用处!因此给你展示一个测试类使用该代码的例子

@Test
public void serviceProxy() {
    IService serviceA = new ServiceProxy(new ServiceA());//@1
    IService serviceB = new ServiceProxy(new ServiceB()); //@2
    serviceA.m1();
    serviceA.m2();
    serviceA.m3();
 
    serviceB.m1();
    serviceB.m2();
    serviceB.m3();
}

        我们将创建一个IService接口类型的引用变量,并指向了ServiceProxy类的实例,并且在ServiceProxy类的构造函数里传入了被代理访问的对象,因此我们不需要在原有代码上修改!直接将需要添加的功能放在ServiceProxy类代理中即可。

测试结果:

问题:代理的确很方便,不用修改原代码即可添加新功能!可是这样我岂不是得给每一个接口实现一个代理类吗,那我不得累死!

答:针对以上问题,我们提供了两种方式:JDK动态代理CGLIB代理

JDK动态代理

        JDK为实现代理提供了两种类,

java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler

使用两种类虽然可以为接口创建代理类,但不能给具体的类创建代理类,也就是指向下转型。

java.lang.reflect.Proxy

        该类是jdk动态代理最重要的类,我们先了解以下其中的静态方法!

  1. newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 这是创建动态代理对象最常用的方法。它接受三个参数:

    • loader:类加载器,用于加载代理类。
    • interfaces:代理类要实现的接口列表。
    • h:实现了 InvocationHandler 接口的对象,用于处理方法调用。
  2. getProxyClass(ClassLoader loader, Class<?>... interfaces) 这个方法返回一个代理类的 Class 对象,但不创建实例。通常很少直接使用,而是更常用的是 newProxyInstance() 方法。它接受两个参数:

    • loader:类加载器,用于加载代理类。
    • interfaces:代理类要实现的接口列表。
  3. isProxyClass(Class<?> cl) 检查指定的 Class 对象是否是代理类的类对象。如果是代理类,返回 true,否则返回 false

  4. getInvocationHandler(Object proxy) 返回与指定代理对象关联的 InvocationHandler。如果代理对象不是动态代理对象或者没有关联 InvocationHandler,则返回 null

方法已经了解过了,接下来是代码阶段

方法一:

1.调用Proxy.getProxyClass方法获取代理类的Class对象
2.使用InvocationHandler接口创建代理类的处理器
3.通过代理类和InvocationHandler创建代理对象
4.上面已经创建好代理对象了,接着我们就可以使用代理对象了

1.接口IService:

public interface IService {
    void m1();
    void m2();
    void m3();
}

 注:存在实现类ServiceA和ServiceB

2.创建代理对象:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 创建 ServiceA 实例
        ServiceA serviceA = new ServiceA();

        // 创建 InvocationHandler 实例
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                try {
                    // 使用反射调用 ServiceA 类的对应方法
                    Method serviceAMethod = ServiceA.class.getMethod(method.getName(), method.getParameterTypes());
                    return serviceAMethod.invoke(serviceA, args);
                } catch (NoSuchMethodException e) {
                    System.out.println("ServiceA 类中没有对应的方法:" + method.getName());
                    // 可以执行一些默认逻辑或者抛出异常
                    return null;
                }
            }
        };

        // 获取接口对应的代理类
        Class<IService> proxyClass = (Class<IService>) Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);

        // 创建代理实例
        IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);

        // 调用代理的方法
        proxyService.m1();
        proxyService.m2();
        proxyService.m3();
    }
}

方法二:

1.使用InvocationHandler接口创建代理类的处理器
2.使用Proxy类的静态方法newProxyInstance直接创建代理对象
3.使用代理对象

更简便创建代理对象代码:

@Test
public void m2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    // 创建 ServiceA 实例
        ServiceA serviceA = new ServiceA();
    // 1. 创建代理类的处理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                    // 使用反射调用 ServiceA 类的对应方法
                    Method serviceAMethod = ServiceA.class.getMethod(method.getName(), method.getParameterTypes());
                    return serviceAMethod.invoke(serviceA, args);
                } catch (NoSuchMethodException e) {
                    System.out.println("ServiceA 类中没有对应的方法:" + method.getName());
                    // 可以执行一些默认逻辑或者抛出异常
                    return null;
                }
        }
    };
    // 2. 创建代理实例
    IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[]{IService.class}, invocationHandler);
    // 3. 调用代理的方法
    proxyService.m1();
    proxyService.m2();
    proxyService.m3();
}

注:如果不太明白这两段代码,建议学习一下Java的反射!

 通用代理对象实现

           通过两个方法,我们可以实现 一个简便通用的代理对象类!

我们需要自定义一个类实现InvocationHandler接口。

创建 InvocationHandler接口

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
public class CostTimeInvocationHandler implements InvocationHandler {
 
    private Object target;
 
    public CostTimeInvocationHandler(Object target) {
        this.target = target;
    }
    // 该重写方法自动调用   属性:代理对象本身    方法     方法的参数  
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(this.target, args);
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
        return result;
    }
 
    /**
     * 用来创建targetInterface接口的代理对象
     *
     * @param target          需要被代理的对象
     * @param targetInterface 被代理的接口
     * @param <T>
     * @return
     */
    public static <T> T createProxy(Object target, Class<T> targetInterface) {
        if (!targetInterface.isInterface()) {
            throw new IllegalStateException("targetInterface必须是接口类型!");
        } else if (!targetInterface.isAssignableFrom(target.getClass())) {
            throw new IllegalStateException("target必须是targetInterface接口的实现类!");
        }
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CostTimeInvocationHandler(target));
    }
}

上面主要是createProxy方法用来创建代理对象,2个参数:

target:目标对象,需要实现targetInterface接口

targetInterface:需要创建代理的接口

invoke方法中通过method.invoke(this.target, args)调用目标方法,然后统计方法的耗时。

测试代码:

@Test
public void costTimeProxy() {
    IService serviceA = CostTimeInvocationHandler.createProxy(new ServiceA(), IService.class);
    IService serviceB = CostTimeInvocationHandler.createProxy(new ServiceB(), IService.class);
    serviceA.m1();
    serviceA.m2();
    serviceA.m3();
 
    serviceB.m1();
    serviceB.m2();
    serviceB.m3();
}

测试结果:

而如果我们还有其他接口和实现类也想统计耗时功能,我们则不需要创建新的代理类,案例如下:

1.创建一个  IUserService 接口

public interface IUserService {
    /**
     * 插入用户信息
     * @param name
     */
    void insert(String name);
}

2.IUserService接口实现类:

public class UserService implements IUserService {
    @Override
    public void insert(String name) {
        System.out.println(String.format("用户[name:%s]插入成功!", name));
    }
}

测试代码:

@Test
public void userService() {
    IUserService userService = CostTimeInvocationHandler.createProxy(new UserService(), IUserService.class);
    userService.insert("路人甲Java");
}

注:

  1. jdk中的Proxy只能为接口生成代理类,如果你想给某个类创建代理类,那么Proxy是无能为力的,此时需要我们用到下面要说的cglib了。

  2. Proxy类中提供的几个常用的静态方法大家需要掌握

  3. 通过Proxy创建代理对象,当调用代理对象任意方法时候,会被InvocationHandler接口中的invoke方法进行处理,这个接口内容是关键


 CGLIB代理

1.什么是CGLIB呢?

        CGLIB(Code Generation Library)是一个强大的代码生成库,它可以在运行时生成字节码,用于创建类的动态代理对象。与Java标准库中的动态代理相比,CGLIB更加灵活,因为它可以代理没有实现接口的类。

        CGLIB通过继承目标类来创建代理对象,然后重写目标类中的方法,实现代理逻辑。这种方式与Java标准库中的动态代理使用代理接口不同,因此CGLIB可以代理那些没有实现接口的类。

        使用CGLIB创建代理对象的过程相对复杂,因为它涉及到字节码的生成和类加载等底层操作。但是,它提供了更大的灵活性,可以代理更多类型的类,并且性能相对较高,因为它不需要通过反射来调用目标方法。

         CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解

2.与JDK代理有什么区别呢?

CGLIB代理JDK代理
接口代理支持可以代理没有实现接口的类代理实现了接口的类,因为它是基于接口的
性能通过继承目标类来创建代理对象,因此在创建代理对象时会涉及到类的继承和方法重写,可能会导致创建代理对象的性能略低于JDK动态代理。利用了Java标准库中的反射机制,相对而言创建代理对象的性能可能更高一些。
依赖需要额外的库依赖Java标准库的一部分,因此无需引入额外的依赖
兼容性JDK动态代理是Java标准库的一部分,因此在Java应用程序中使用广泛,且具有良好的兼容性。不是Java标准库的一部分,因此在一些特定的环境中可能需要注意兼容性和依赖管理。

        虽然CGLIB提供了更多的灵活性和功能,但JDK动态代理仍然是Java开发中常用的一种代理方式,特别是在需要代理接口实现的情况下。选择使用哪种代理方式取决于具体的需求和场景。

话语一大堆,不如实际代码来的可观!得先建立Spring项目

3.项目建立

1.引入依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

POM文件中引入依赖,下载需要等待时间

2.代码示例:

public class SampleClass {
    public void test(){
        System.out.println("hello world");
    }

    public static void main(String[] args) {
        // 创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        // 设置父类(目标类)
        enhancer.setSuperclass(SampleClass.class);
        // 设置回调处理器
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("before method run...");
                //调用代理对象的方法
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("after method run...");
                return result;
            }
        });
        // 创建代理对象
        SampleClass sample = (SampleClass) enhancer.create();
        // 调用代理对象的方法
        sample.test();
    }
}

在mian函数中,我们通过一个Enhancer和一个MethodInterceptor来实现对方法的拦截

4.CGLIB常用API

Enhancer类

        CGLIB中最常用的一个类,Enhancer既能够代理普通的class,也能够代理接口

        Enhancer可创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,

代码示例:

先创建一个普通类:

public class Demo {
    public String test(){
        return "我是普通方法";
    }
    public final String test2(){
        return "我是final方法";
    }
}

以这个类为基础,测试:

 @Test
    void testCGLIB(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Demo.class);
        enhancer.setCallback(new FixedValue() {
            @Override
            public Object loadObject() throws Exception {
                return "我爱cglib";
            }
        });
        Demo demo = (Demo) enhancer.create();
        System.out.println(demo.test());
        System.out.println(demo.test2());
    }

 

注:test方法被拦截,test2方法没有被拦截 

解释:使用 setCallback() 方法设置了一个 FixedValue 类型的,回调重写了 loadObject() 方法。在这个方法中,我们指定了固定的返回值 "我爱cglib",也就是指代理对象方法被调用的时候,只会返回一个 "我爱cglib" ,但是无法拦截代理对象的final方法。

但是假设我们想要特定方法进行拦截,怎么操作呢?

普通类:

public class Demo {
    public String test(){
        return "我是普通方法1";
    }
    public  String test2(){
        return "我是普通方法2";
    }
}

测试类方法:

@Test
public void testCallbackFilter() throws Exception{
     Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Demo.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                if (!method.getName().equals("test2")) {
                    // 放行 非test()2 方法,直接调用原始方法
                    System.out.println("Intercepted method: " + method.getName() + " 可以运行");
                    return proxy.invokeSuper(obj, args);
                } else {
                    // 阻拦test2方法
                    System.out.println("Intercepted method: " + method.getName() + " - 不可以运行");
                    // 这里可以根据需要返回一个默认值或者抛出异常
                    return null; // 返回 null,表示阻拦方法调用
                }
            }
        });
        Demo d = (Demo) enhancer.create();
        d.test();
        d.test3();
}

例如我们想要阻拦test2方法,则如此!


其实还有很多其他类可以使用,但是我们主要了解最常用的 Enhancer类

其他类: 

ImmutableBean类

        不可变的Bean,ImmutableBean允许创建一个原来对象的包装类,这个包装类是不可变的,任何改变底层对象的包装类操作都会抛出IllegalStateException。但是我们可以通过直接操作底层对象来改变包装类对象。

Bean generator类

cglib提供的一个操作bean的工具,使用它能够在运行时动态的创建一个bean。

Bean Copier类

cglib提供的能够从一个bean复制到另一个bean中,而且其还提供了一个转换器,用来在转换的时候对bean的属性进行操作。

BulkBean类

相比于BeanCopier,BulkBean将copy的动作拆分为getPropertyValues和setPropertyValues两个方法,允许自定义处理属性

注:CGLIB的大部分类是直接对Java字节码进行操作!

标签:IService,Spring,void,System,模式,代理,方法,public
From: https://blog.csdn.net/m0_74097410/article/details/137289908

相关文章

  • 基于Java+SpringBoot+Vue前后端分离大学生校园兼职求职招聘信息系统(适合毕业设计项目
    文章目录目录前言一、系统设计1、系统运行环境2.系统架构设计二、系统核心技术三.系统功能实现四.实现代码五.源码获取前言 本文旨在探讨并设计一个基于Springboot框架的大学生校园兼职求职招聘信息系统。该系统的设计与实现,旨在满足大学生寻找兼职工作的......
  • SpringBoot——测试
    SpringBoot测试源码在test-springboot测试无非就是设定预期值与真实值比较,相同则测试通过,不同则测试失败Ctrl+鼠标左键看源码,再按ctrl:f12查看方法0、环境<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://ww......
  • SpringBoot集成jasypt,加密yml配置文件
    一、Jasypt简介Jasypt是一个Java简易加密库,用于加密配置文件中的敏感信息,如数据库密码。jasypt库与springboot集成,在实际开发中非常方便。1、JasyptSpringBoot为springboot应用程序中的属性源提供加密支持,出于安全考虑,Springboot配置文件中的敏感信息通常需要对它进......
  • springboot本地打war包
    1、pom.xml修改图里标注的①②,新增③,图片下面有我打包的pom.xml大家可以参考<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:s......
  • 「Java开发指南」如何利用MyEclipse启用Spring DSL?(一)
    本教程将引导您通过启用SpringDSL和使用ServiceSpringDSL抽象来引导Spring和Spring代码生成项目,本教程中学习的技能也可以很容易地应用于其他抽象。在本教程中,您将学习如何:为SpringDSL初始化一个项目创建一个模型包创建一个服务和操作实现一个服务方法启用JAX-WS和DWR......
  • 基于SpringBoot+MySQL+SSM+Vue.js的生鲜在线销售系统(附论文)
    演示视频基于SpringBoot+MySQL+SSM+Vue.js的生鲜在线销售系统技术描述开发工具:Idea/Eclipse数据库:MySQLJar包仓库:Maven前端框架:Vue/ElementUI后端框架:Spring+SpringMVC+Mybatis+SpringBoot文字描述基于SpringBoot+MySQL+SSM+Vue.js的生鲜在线销售系统(附......
  • 基于SpringBoot+MySQL+SSM+Vue.js的招聘系统(附论文)
    演示视频基于SpringBoot+MySQL+SSM+Vue.js的招聘系统技术描述开发工具:Idea/Eclipse数据库:MySQLJar包仓库:Maven前端框架:Vue/ElementUI后端框架:Spring+SpringMVC+Mybatis+SpringBoot文字描述基于SpringBoot+MySQL+SSM+Vue.js的招聘系统(附论文),用户,管理员......
  • java计算机毕业设计房地产公司员工工单管理系统(附源码+springboot+开题+论文+部署)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着房地产行业的迅猛发展,企业内部的运营和管理面临着越来越高的要求。特别是在员工工单管理方面,传统的纸质工单或简单的电子记录方式已经无法满足企......
  • java计算机毕业设计房产中介管理(附源码+springboot+开题+论文+部署)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在当前的房地产市场环境下,房产中介作为连接买卖双方的重要桥梁,其运营效率和服务质量直接影响到市场的健康发展和客户的满意度。然而,传统的房产中介管......
  • 【SpringBoot + Tomcat】请求到达后端服务进程后的处理过程
    1 前言这节我主要是想看下,Tomcat如何接收到请求并且是怎么一步步封装并交给SpringMVC处理的。这块之前一直没太深入的了解过,所以这节我们来看看。在看这节之前,你首先要清楚这两个问题,方便你更好的去理解。(1)SpringBoot启动的过程中,Tomcat的创建和启动时机是在什么时候呢?不......