探索【注解】、【反射】、【动态代理】,深入掌握高级 Java 开发技术

文章目录

  • Java注解
    • 1.注解基础
    • 2.注解原理
  • 反射
    • 1.Class对象的获取
      • 1.基础公共类
      • 1.1.Object => getClass()
      • 1.2.类名.class 的方式
      • 1.3.Class.forName()
    • 2.获取类的成员变量
    • 3.获取成员方法并调用
    • 4.反射优缺点
  • 代理
    • 1.结构
    • 2.静态代理
      • 2.1.案例1-计算前后校验
        • 2.1.1.创建接口
        • 2.1.2.创建实现类
        • 2.1.2.创建代理类
        • 2.1.3.将代理对象交给Spring管理
        • 2.1.4.测试
      • 2.2.优点
      • 2.3.缺点
    • 3.动态代理
      • 3.1.JDK的动态代理
        • 3.1.1.实现
        • 3.1.2.创建接口
        • 3.1.3.创建实现类
        • 3.1.4.创建代理类
        • 3.1.5.创建测试类
        • 3.1.6.优缺点
      • 3.2.CGLib代理
        • 3.2.1.实现
        • 3.2.2.接口
        • 3.2.3.实现类
        • 3.2.4.代理类
        • 3.2.5.代理工厂类
        • 3.2.6.测试类
        • 3.2.7.代理流程图
      • 3.3.JDK和CGLib对比

Java注解

其实注解模块属于Java,不属于SpringBoot模块,但是为了方便,还是放在这个模块下

1.注解基础

# 注解含义注解其实就是 标注与解析,在我们的Java代码中,注解无处不再,我们也是无时不在使用,使用的太多,被忽略的理所当然。无论是在JDK源码或者框架组件,都在使用注解能力完成各种识别和解析动作;在对系统功能封装时,也会依赖注解能力简化各种逻辑的重复实现;在Annotation的源码注释中有说明:所有的注解类型都需要继承该公共接口,本质上看注解是接口,但是代码并没有显式声明继承关系,可以直接查看字节码文件;

在这里插入图片描述

import java.lang.annotation.*;/*** Documented:是否被javadoc或类似工具记录在文档中;*/
@Documented
/*** Inherited:标识注解是否可以被子类继承*/
@Inherited
/*** Target:作用目标,在ElementType枚举中可以看到取值包括类、方法、属性等;*/
@Target(ElementType.METHOD)/*** Retention:保留策略,比如编译阶段是否丢弃,运行时保留;*/
@Retention(RetentionPolicy.RUNTIME)
/*** 此处声明一个SysLogInfo注解,作用范围是在方法上,并且在运行时保留,该注解通常用在服务运行时,结合AOP切面编程实现方法的日志采集;*/
public @interface  SysLogInfo {
}

2.注解原理

  • method.getAnnotation(SysLogInfo.class)
    1. method 是一个 Method 类型的对象,代表了一个方法。
    2. getAnnotation(Class<T> annotationClass)AnnotatedElement 接口中的一个方法,用于获取特定类型的注解
    3. SysLogInfo.class 是你要获取的注解的类型,这里是 SysLogInfo 注解类的类对象。

从简单的入门开始

public class AnnotationDemo {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());// 定义一个带有自定义注解的方法@SysLogInfo(name = "测试模块")public static void main(String[] args) {// 启用保存生成的代理文件,用于调试System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");// 使用反射获取AnnotationDemo类的所有方法Method[] methods = AnnotationDemo.class.getMethods();// 遍历每个方法for (Method method : methods) {// 检查方法是否有SysLogInfo注解SysLogInfo annotation = method.getAnnotation(SysLogInfo.class);if (annotation != null) {// 记录带注解的方法信息logger.error("方法名: {}", method.getName());logger.error("模块: {}", annotation.name());}}}
}
# 注意 这里回涉及两个概念 反射机制 动态代理(可自行百度学习)反射:可以让程序在运行时获取类的完整结构信息代理:给目标对象提供一个代理对象,让代理对象持有目标对象的引用案例中通过反射机制,在程序运行时进行注解的获取和解析,值得关注的是SysLogInf对象的类名,输出的是代理类信息案例执行完毕后,会在代码工程的目录下生成代理类,可以查看$Proxy2文件

在这里插入图片描述

// ********** 略 **********  
public final class $Proxy2 extends Proxy implements SystemLog {public final String model() throws  {try {return (String)super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}
}
// ********** 略 **********  
// 在对SystemLog解析的过程中,实际上是在使用注解的代理类,$Proxy2继承了Proxy类并实现了SysLogInf接口,并且重写了相关方法
// 值得一看是代理类中invoke方法调用,具体的处理逻辑在AnnotationInvocationHandler类的invoke方法中,会对注解原生方法和自定义方法做判断,并对原生方法提供实现,可自行阅读源码
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String name = method.getName();if (Object.class == method.getDeclaringClass()) {if ("equals".equals(name)) {Object obj = args[0];return this.checkEquals(obj);}if ("toString".equals(name)) {return this.annotation.toString();}if ("hashCode".equals(name)) {return this.hashCode();}} else if ("annotationType".equals(name) && method.getParameterTypes().length == 0) {return this.getAnnotationType();}MemberValue mv = this.annotation.getMemberValue(name);return mv == null ? this.getDefault(name, method) : mv.getValue(this.classLoader, this.pool, method);
}

反射

什么是反射?

Java反射机制在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态的获取信息以及动态的调用对象的方法的功能我们称之为Java的反射机制

eg:类的class声明(class对象),变量(field),方法(method)等等信息,利用反射技术可以对一个类进行解剖,动态获取信息进行处理。

1.Class对象的获取

1.基础公共类

/*** 学生类* 基础方式不写注释了*/
@Getter @Setter
class Student implements Serializable {public Student(){}private String userName = "default_user_name";private String age;private String className;public String eMail;/*** 获取学生信息 加密用户名称* @return 学生信息*/public String encodingStudent(String user) {return "大大怪-----" + user + " execute "+ "Student{" +"userName='" + MD5Utils.getInstance().getMD5(userName) + '\'' +", age='" + age + '\'' +", className='" + className + '\'' +", eMail='" + eMail + '\'' +'}';}private String getStudentInfo() {return "Student{" +"userName='" + userName + '\'' +", age='" + age + '\'' +", className='" + className + '\'' +", eMail='" + eMail + '\'' +'}';}
}

1.1.Object => getClass()

public class ReflectBaseDemo {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());@Testpublic void testReflectBase() {// new的时候会创建一个Student对象,一个Class对象.两个对象。// 一个类对应一个Class对象,一个类只会被加载一次,一个类只会有一个Class对象;Student student = new Student();// 其中 ? extends Student 表示Student类及其子类Class<? extends Student> studentClass = student.getClass();logger.error("student.getClass() = {}", studentClass);}
}

在这里插入图片描述

1.2.类名.class 的方式

	@Test@DisplayName("反射基础练习-通过.class获取对象")public void testReflectBase2() {// new的时候会创建一个Student对象,一个Class对象.两个对象。// 一个类对应一个Class对象,一个类只会被加载一次,一个类只会有一个Class对象;Student student = new Student();// 通过.class获取对象,这种方式是最安全可靠的,因为在编译时就会检查是否有这个类,如果没有就会报错,不会运行时报错,所以这种方式最安全可靠。Class<Student> studentClass = Student.class;logger.error("Student.class = {}", studentClass);}

在这里插入图片描述

1.3.Class.forName()

	@Test@DisplayName("通过Class.forName获取对象")public void testReflectBase3() throws ClassNotFoundException {// 通过Class.forName获取对象,这种方式是最不安全的,因为在编译时不会检查是否有这个类,如果没有就会运行时报错,所以这种方式最不安全。// 但是这个方法使用最多,因为这个方法可以通过配置文件来获取类名。// 这样就可以在不修改代码的情况下,通过修改配置文件来修改类名,这样就可以实现动态加载类。try {Class<?> studentClass = Class.forName("com.hrfan.java_se_base.base.reflect.base.Student");logger.error("Student.class = {}", studentClass);}catch (ClassNotFoundException e) {throw new RuntimeException("class not found!",e);}}

在这里插入图片描述

在这里插入图片描述

2.获取类的成员变量

  • class.getDeclaredFields() 获取所有字段 包括私有字段
  • class.getFields() 获取公共字段
  • field.set(obj,“xxxxxx”) 给字段进行赋值
    1. field 是一个 Field 类型的对象,代表了一个字段(成员变量)。
    2. set()Field 类中的一个方法,用于设置特定对象的字段的值。
    3. obj 是要操作的对象实例。
    4. "xxxxxx" 是要设置的字段值。
	@Test@DisplayName("获取类的属性以及成员变量")public void testReflectBase4(){try {Class<?> studentClass = Class.forName("com.hrfan.java_se_base.base.reflect.base.Student");logger.error("==================== 获取所有字段(包括private)====================");// 获取所有的字段.注意 getFields私有字段是获取不到的,如果要获取私有字段,需要使用getDeclaredFields方法for (Field field : studentClass.getDeclaredFields()) {logger.error("field = {}", field);}logger.error("==================== 获取公共字段 ====================");for (Field field : ) {logger.error("field = {}", field);}logger.error("==================== 获取指定公共字段,并赋值 ====================");try {Field field = studentClass.getField("eMail");logger.error("get field = {}", field);// 通过反射构造对象try {// 通过反射获取构造方法,并创建对象(注意 getDeclaredConstructor 是获取所有的构造方法,包括私有的,如果要获取公共的构造方法,需要使用getConstructor方法)Object obj = studentClass.getDeclaredConstructor().newInstance();// 通过反射给字段赋值field.set(obj,"安徽省阜阳市-xxxxxxxx");Student student = (Student) obj;logger.error("==================== 验证 ====================");// 验证logger.error("student = {}", student.getStudentInfo());} catch (Exception e) {throw new RuntimeException("constructor not found!",e);}} catch (NoSuchFieldException e) {throw new RuntimeException("filed not found!",e);}} catch (ClassNotFoundException e) {throw new RuntimeException("class not found!",e);}}
}

在这里插入图片描述

3.获取成员方法并调用

  • class.getDeclaredMethods() 获取所有方法
  • class.getMethods() 获取公共方法
  • class.getDeclaredMethod(“xxxx”) 获取指定私有方法
  • **Method m = studentClass.getMethod(“xxxxx”,String.class) 获取指定公共方法,后面是参数类型 **
  • Object object = studentClass.getConstructor().newInstance() 通过反射构造对象
    1. studentClass 是一个 Class 对象的引用,代表了一个类的定义。
    2. getConstructor()Class 类中的一个方法,它返回指定类中的公共构造方法,其中不需要传递任何参数。如果你的类中没有明确的定义无参构造方法,这个方法将抛出 NoSuchMethodException 异常。
    3. 一旦获得了构造方法,newInstance() 方法会使用该构造方法创建新的对象实例。
    4. 返回的 Object 引用 obj 就是通过反射机制创建的新对象的引用。
  • m.invoke(object, “xxxxxx”) 通过反射执行方法
    1. m 是一个 Method 类型的对象,代表了一个方法。
    2. invoke()Method 类中的一个方法,用于调用特定对象的方法。
    3. object 是要调用方法的对象实例。
    4. "xxxxxx" 是作为参数传递给该方法的参数。
@Test@DisplayName("获取类的成员方法")public void testReflectBase5(){try {Class<?> studentClass = Class.forName("com.hrfan.java_se_base.base.reflect.base.Student");logger.error("==================== 获取所有方法(包括private)====================");for (Method declaredMethod : studentClass.getDeclaredMethods()) {logger.error("declaredMethod = {}", declaredMethod);}logger.error("==================== 获取公共方法 ====================");// 此时会把Object类的方法也获取到,因为Student类继承了Object类,所以会把Object类的方法也获取到for (Method method : studentClass.getMethods()) {logger.error("method = {}", method);}logger.error("==================== 获取指定私有方法 ====================");Method getStudentInfo = null;try {// 后面接的是方法参数类型,如果有多个参数,就写多个,如果没有参数,就不写getStudentInfo = studentClass.getDeclaredMethod("getStudentInfo");logger.error("getStudentInfo = {}", getStudentInfo);} catch (NoSuchMethodException e) {throw new RuntimeException("methods not found!",e);}logger.error("==================== 获取指定公共方法 ====================");Method encodingStudent = null;try {// 后面接的是方法参数类型,如果有多个参数,就写多个,如果没有参数,就不写encodingStudent = studentClass.getMethod("encodingStudent",String.class);logger.error("encodingStudent = {}", encodingStudent);try {// 通过反射构造对象,注意这里是获取公共的构造方法,如果要获取私有的构造方法,需要使用getDeclaredConstructor方法Object obj = studentClass.getConstructor().newInstance();// 通过反射调用方法String res = (String) encodingStudent.invoke(obj, "张三");logger.error("==================== 验证(调用公共方法 ) ====================");logger.error("res = {}", res);} catch (Exception e) {throw new RuntimeException("constructor not found!",e);}} catch (NoSuchMethodException e) {throw new RuntimeException("methods not found!",e);}logger.error("==================== (暴力-解除私有限制) ====================");// 通常情况下,使用 setAccessible(true) 是为了允许访问那些由于访问修饰符限制而无法直接访问的成员。然而,需要谨慎使用,因为它会绕过 Java 的安全检查。getStudentInfo.setAccessible(true);// 通过反射构造对象,注意这里是获取公共的构造方法,如果要获取私有的构造方法,需要使用getDeclaredConstructor方法Object obj = null;try {obj = studentClass.getConstructor().newInstance();// 通过反射调用方法String res = (String) getStudentInfo.invoke(obj);getStudentInfo.invoke(obj);logger.error("res = {}", res);} catch (Exception e) {throw new RuntimeException("constructor not found!",e);}}catch (ClassNotFoundException e) {throw new RuntimeException("class not found!",e);}}

4.反射优缺点

优点

  1. 反射提高了Java程序的灵活性扩展性降低耦合性,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类;

缺点

  1. 性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要慢于直接代码。
  2. 使用反射会模糊程序内部逻辑:程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。
  3. 反射增加了安全问题:比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。

代理

代理模式(Proxy Pattern)是 23 种设计模式的一种,属于结构型模式。他指的是一个对象本身不做实际的操作,而是通过其他对象来得到自己想要的结果。这样做的好处是可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能

代理的关键点就是代理对象和目标对象的关系

这里能体现出一个非常重要的编程思想:不要随意去改源码,如果需要修改,可以通过代理的方式来扩展该方法

场景:(如在调用目标方法前校验权限,在调用完目标方法后打印日志等)等功能。

1.结构

  • 接口:接口中的方法是要真正去实现的。
  • 被代理类:实现上述接口,这是真正去执行接口中方法的类。
  • 代理类:同样实现上述接口,同时封装被代理类对象,帮助被代理类去实现方法

在这里插入图片描述

2.静态代理

静态代理是一种设计模式,它在编译时就确定了代理类和被代理类的关系。在静态代理中,代理类是由程序员创建的,手动编写的。

在这里插入图片描述

2.1.案例1-计算前后校验

2.1.1.创建接口
public interface Calculator {/*** 加法* @param a 第一个加数* @param b 第二个加数* @return 两个加数的和*/int add(int a, int b);
}
2.1.2.创建实现类
/*** 加法实现类* @author 13723* @version 1.0* 2024/2/1 20:15*/
@Service
public class CalculatorImpl implements Calculator{private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());/*** 加法* @param a 第一个加数* @param b 第二个加数* @return 两个加数的和*/@Overridepublic int add(int a, int b) {return a+b;}
}
2.1.2.创建代理类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.invoke.MethodHandles;/*** @author 13723* @version 1.0* 2024/2/1 20:17*/
public class CalculatorProxy implements Calculator{private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());/*** 被代理对象*/private CalculatorImpl calculatorImpl;/*** 无参构造函数*/public CalculatorProxy(){}/*** 有参构造函数(这里提供是为例 注册代理对象到Spring中时,把被代理的对象传入进来)* @param calculatorImpl 被代理对象*/public CalculatorProxy(CalculatorImpl calculatorImpl) {this.calculatorImpl = calculatorImpl;}/*** 加法* @param a 第一个加数* @param b 第二个加数* @return 两个加数的和*/@Overridepublic int add(int a, int b) {logger.error("================= 代理对象开始执行 校验 =================");// 执行被代理对象的方法,代理对象可以在被代理对象的方法执行前后做一些事情int add = calculatorImpl.add(a, b);logger.error("计算结果:{}",add);logger.error("================= 代理对象结束执行 校验 =================");return add;}
}
2.1.3.将代理对象交给Spring管理
import com.hrfan.java_se_base.base.proxy.cglib.CalculatorImpl;
import com.hrfan.java_se_base.base.proxy.cglib.CalculatorProxy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ProxyCalculatorAutoConfiguration {@Autowiredprivate CalculatorImpl calculator;/*** 代理类交给springboot进行管理,通过注入的方式,传入被代理类,生成代理类  * @return 代理类*/@Beanpublic CalculatorProxy calculatorProxy() {CalculatorProxy calculatorProxy = new CalculatorProxy(calculator);return calculatorProxy;}
}
2.1.4.测试
import com.hrfan.java_se_base.base.proxy.cglib.CalculatorProxy;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;/*** @author 13723* @version 1.0* 2024/2/1 20:19*/
@SpringBootTest
public class CGLibProxyDemo {@Resourceprivate CalculatorProxy proxy;@Test@DisplayName("测试静态代理")public void test(){proxy.add(1,2);}
}

在这里插入图片描述

2.2.优点

  1. 简单易懂:静态代理模式相对简单,易于理解和实现,适合初学者学习和使用。
  2. 编译时类型检查:由于代理类在编译时已经确定,因此可以进行类型检查,避免一些运行时错误。
  3. 适用于简单场景:对于一些简单的需求,静态代理是一种简洁有效的实现方式,例如日志记录、性能监控等。

2.3.缺点

  1. 代码重复:静态代理需要为每一个被代理类编写一个代理类,如果代理类逻辑较多,会导致代码冗余,增加维护成本。
  2. 不灵活:静态代理在编译时确定代理类和被代理类的关系,一旦确定就无法修改,导致扩展性较差。如果需要代理的类过多,静态代理会变得不太实用。
  3. 不支持横切关注点的动态添加:静态代理的横切关注点在编译时已经确定,无法在运行时动态添加新的横切关注点。
  4. 不支持异步调用:静态代理是同步的,无法支持异步调用。

3.动态代理

3.1.JDK的动态代理

JDK动态代理是一种在运行时生成代理类的机制,它允许在代理类中动态地处理方法调用。这种代理机制通过InvocationHandler接口来实现,代理类在运行时通过实现指定接口生成,并且可以在代理类的方法中插入自定义的逻辑。在这个过程中,代理类并不是在编译时就确定的,而是在运行时动态生成的。

  1. InvocationHandler(调用处理器): InvocationHandler 是一个接口,用于定义代理类的具体行为。它包含一个方法 invoke,在代理对象的方法被调用时会被触发。通过实现这个接口,可以在方法调用前后执行自定义的逻辑。
  2. Proxy(代理类): Proxy 类是 JDK 提供的用于创建动态代理类和实例的工具类。通过 newProxyInstance 方法,可以在运行时生成代理对象。这个方法接收一个类加载器、一组接口和一个 InvocationHandler 实现作为参数。
  3. 动态代理类:Proxy 类在运行时动态生成的代理类。这个类实现了指定的接口,并将方法调用委托给 InvocationHandlerinvoke 方法。

JDK动态代理使用步骤:

  1. 定义接口: 首先定义一个业务接口,该接口将由代理类和实际业务类共同实现。
  2. 实现业务类: 创建一个实际的业务类,实现定义的业务接口。
  3. 实现 InvocationHandler: 创建一个实现 InvocationHandler 接口的类,该类将负责在代理对象的方法调用时执行特定逻辑。
  4. 创建代理对象: 使用 Proxy.newProxyInstance 方法创建代理对象,该方法接收类加载器、接口数组和 InvocationHandler 实例作为参数。
  5. 调用代理对象方法: 通过代理对象调用方法,代理对象将在执行方法前后执行 InvocationHandler 的逻辑。
3.1.1.实现

在这里插入图片描述

# 案例每次保存用户前 对用户信息进行校验,保存完成后返回用户信息
3.1.2.创建接口
/*** 用户服务接口* @author 13723* @version 1.0* 2024/2/1 22:19*/
public interface UserServiceBi {/*** 添加用户* @param name 用户名*/void addUser(String name);
}
3.1.3.创建实现类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;import java.lang.invoke.MethodHandles;/*** 用户服务实现类* @author 13723* @version 1.0* 2024/2/1 22:30*/
@Service
public class UserServiceBiImpl implements UserServiceBi {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());/*** 添加用户* @param name 用户名*/@Overridepublic void addUser(String name) {logger.error("添加用户成功!用户名:{}",name);}
}
3.1.4.创建代理类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** 用户服务代理类* @author 13723* @version 1.0* 2024/2/1 22:31*/
public class UserServiceProxy implements InvocationHandler {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());/*** 被代理对象*/private UserServiceBiImpl userService;/*** 将代理对象注入到代理类中* 多个线程可能会同时尝试创建代理对象,而代理对象的创建过程是不可变的,因此它本身是线程安全的。* 但是,如果 userService 对象的状态会在代理对象创建之后被修改,那么就有可能出现线程安全问题* 这里我们将 UserServiceProxy 的构造函数私有化,不允许外部直接创建实例。然后,通过 newInstance 工厂方法来创建代理对象。* 在 invoke 方法中,我们使用了同步块来确保在多线程环境下的线程安全性。* 这种方式保证了在实例化代理对象时的线程安全,避免了多个线程同时创建代理对象的问题。* @param userService 被代理对象*/private UserServiceProxy(UserServiceBiImpl userService) {this.userService = userService;}public static UserServiceBi newInstance(UserServiceBiImpl userService){// 生成代理对象,这里的代理对象是在内存中生成的,不是在磁盘上生成的,所以不能直接通过反射获取,需要通过Proxy.newProxyInstance()方法获取// 第一个参数:被代理对象的类加载器,这里使用被代理对象的类加载器,因为代理对象和被代理对象在同一个类加载器中,这样可以保证代理对象和被代理对象在同一个类加载器中// 第二个参数:被代理对象的接口,这里使用被代理对象的接口,因为代理对象和被代理对象实现了同一个接口,这样可以保证代理对象和被代理对象实现了同一个接口// 第三个参数:代理对象,这里使用代理对象,因为代理对象实现了InvocationHandler接口,这样可以保证代理对象实现了InvocationHandler接口return (UserServiceBi) Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),new UserServiceProxy(userService));}/*** 在代理实例上处理方法调用并返回结果* @param proxy the proxy instance that the method was invoked on** @param method the {@code Method} instance corresponding to* the interface method invoked on the proxy instance.  The declaring* class of the {@code Method} object will be the interface that* the method was declared in, which may be a superinterface of the* proxy interface that the proxy class inherits the method through.** @param args an array of objects containing the values of the* arguments passed in the method invocation on the proxy instance,* or {@code null} if interface method takes no arguments.* Arguments of primitive types are wrapped in instances of the* appropriate primitive wrapper class, such as* {@code java.lang.Integer} or {@code java.lang.Boolean}.** @return the value to return from the method invocation on the* @throws Throwable the exception to throw from the method*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {logger.error("执行保存用户前-校验用户数据!");Object result = method.invoke(userService, args);logger.error("执行保存用户后-返回用户数据!");return result;}
}
3.1.5.创建测试类
import com.hrfan.java_se_base.base.proxy.jdk_proxy.UserServiceBi;
import com.hrfan.java_se_base.base.proxy.jdk_proxy.UserServiceBiImpl;
import com.hrfan.java_se_base.base.proxy.jdk_proxy.UserServiceProxy;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;
import java.lang.invoke.MethodHandles;/*** @author 13723* @version 1.0* 2024/2/1 22:42*/
@SpringBootTest
public class JDKProxyTest1 {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());@Autowiredprivate UserServiceBiImpl userService;@Testpublic void test1(){UserServiceBi userProxy = UserServiceProxy.newInstance(userService);userProxy.addUser("喜羊羊!");}
}

在这里插入图片描述

3.1.6.优缺点

JDK动态代理的优点:

  1. 无需手动编写代理类: 相较于静态代理,JDK动态代理无需手动编写代理类,减少了代码冗余。
  2. 支持多个接口代理: 代理类可以实现多个接口,灵活性较高。
  3. 运行时动态生成: 代理类的生成是在运行时进行的,使得系统更加灵活和可扩展。

JDK动态代理的缺点:

  1. 只能代理接口: JDK动态代理只能代理接口,无法代理类。
  2. 运行效率相对较低: 由于代理类的生成和方法调用过程都需要运行时处理,相对于静态代理或者CGLib动态代理,JDK动态代理的效率较低。

应用场景:

  1. AOP(面向切面编程): JDK动态代理常用于实现AOP,通过在方法调用前后添加日志记录、事务管理、性能监控等功能,实现了横切关注点的统一管理。
  2. Spring框架中的事务管理: Spring框架通过动态代理实现了声明式事务管理,对被@Transactional注解修饰的方法进行代理,从而实现了事务管理的功能。
  3. 远程方法调用(RPC): JDK动态代理可以在远程方法调用时,通过网络传输代理对象,实现客户端和服务端之间的通信。
  4. 安全检查: 在方法调用前后进行安全检查,比如权限验证、参数校验等。
  5. 性能监控: 在方法调用前后记录方法执行时间,进行性能监控和分析。
  6. 缓存代理: 在方法调用前先检查缓存中是否存在结果,如果存在则直接返回缓存结果,否则执行方法并缓存结果。

3.2.CGLib代理

CGlib(Code Generation Library)是一个强大的,高性能的代码生成类库,它通过动态生成字节码来创建代理对象。相对于 JDK 动态代理而言,CGlib 提供了更多的功能和灵活性。

CGLib使用步骤可以包括

  1. 创建Enhancer对象:首先,我们创建一个Enhancer对象,它是CGLIB库的核心类之一,用于设置代理对象的属性和行为。
  2. 设置父类:使用setSuperclass()方法设置要代理的对象的类。CGLIB通过继承的方式创建代理对象,因此需要指定一个父类。
  3. 设置拦截器:通过setCallback()方法设置代理对象的拦截器。拦截器是一个实现了MethodInterceptor接口的对象,它定义了在方法调用前后进行的额外操作,比如日志记录、性能监控等。
  4. 创建代理对象:调用Enhancer对象的create()方法来创建代理对象。在这一步,CGLIB会动态生成一个代理类,并覆盖父类中的方法来添加拦截器的逻辑。
  5. 返回代理对象:返回创建的代理对象。这个代理对象实际上是在运行时动态生成的一个子类,它继承自被代理类,并且在方法调用时会先执行拦截器的逻辑,然后再调用被代理类的方法。
创建Enhancer对象
设置要代理的对象的类
设置代理对象的拦截器
动态生成代理类并添加拦截器逻辑
返回创建的代理对象
3.2.1.实现

在这里插入图片描述

# 保存用户前对信息进行校验,然后保存完数据,返回用户信息
3.2.2.接口
/*** 客户服务接口* @author 13723* @version 1.0* 2024/2/2 7:40*/
public interface ClientService {/*** 保存客户信息* @param username 客户名称*/void saveClient(String username);
}
3.2.3.实现类
/*** 客户服务实现类* @author 13723* @version 1.0* 2024/2/2 7:42*/
@Service
public class ClientServiceImpl implements ClientService{private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());/*** 保存客户信息* @param username 客户名称*/@Overridepublic void saveClient(String username) {logger.error(" ----------------- 保存客户基础信息! ----------------- ");}
}
3.2.4.代理类
/*** 客户服务 拦截器类类,用于再方法调用前后 添加日志记录功能* @author 13723* @version 1.0* 2024/2/2 7:43*/
@Service
public class ClientServiceInterceptor implements MethodInterceptor {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());/*** 在代理实例上处理方法调用并返回结果* @param obj 代理对象* @param method 被代理对象的方法* @param args 方法参数* @param methodProxy 代理对象的方法* @return Object 返回值* @throws Throwable 异常*/@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {logger.error("保存用户前-----------------------> 进行数据校验");// 调用被代理对象的方法Object result = methodProxy.invokeSuper(obj, args);logger.error("保存用户后-----------------------> 返回用户信息");return result;}
}
3.2.5.代理工厂类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;import java.lang.invoke.MethodHandles;/*** 客户服务 工厂类(用于创建代理对象)* @author 13723* @version 1.0* 2024/2/2 7:47*/
public class ClientServiceFactory {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());/*** 创建代理对象* @param clazz 要代理的对象* @param interceptor 代理对象的拦截器* @return 代理对象* @param <T> 泛型 T*/public static <T> T createProxy(T clazz, MethodInterceptor interceptor) {// 创建一个增强器,用于创建代理对象,并设置要代理的对象的类,以及代理对象的拦截器,拦截器用于在方法调用前后添加日志记录功能Enhancer enhancer = new Enhancer();// 设置要代理的对象的类,以及代理对象的拦截器enhancer.setSuperclass(clazz.getClass());enhancer.setCallback(interceptor);// 创建代理对象return (T) enhancer.create();}
}
3.2.6.测试类
import com.hrfan.java_se_base.spring_boot.proxy.cglib_proxy.ClientServiceFactory;
import com.hrfan.java_se_base.spring_boot.proxy.cglib_proxy.ClientServiceImpl;
import com.hrfan.java_se_base.spring_boot.proxy.cglib_proxy.ClientServiceInterceptor;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.lang.invoke.MethodHandles;/*** @author 13723* @version 1.0* 2024/2/1 22:42*/
@SpringBootTest
public class CGlibProxyTest1 {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());@Autowiredprivate ClientServiceImpl clientService;@Autowiredprivate ClientServiceInterceptor clientServiceInterceptor;@Testpublic void test1(){// 创建代理对象ClientServiceImpl proxy = ClientServiceFactory.createProxy(clientService, clientServiceInterceptor);// 执行代理对象的方法proxy.saveClient("美羊羊!");}
}

在这里插入图片描述

3.2.7.代理流程图

Spring-CGLib代理.pdf

3.3.JDK和CGLib对比

  1. 实现原理
    • JDK动态代理基于接口,使用Java反射机制实现代理。
    • CGLIB动态代理基于继承,通过生成目标类的子类来实现代理。
  2. 目标类要求
    • JDK动态代理要求目标类必须实现接口。
    • CGLIB动态代理可以代理未实现接口的类。
  3. 性能
    • JDK动态代理的性能相对较低,因为它基于反射调用目标方法。
    • CGLIB动态代理的性能相对较高,因为它生成的代理类是目标类的子类,直接调用目标方法。
  4. 依赖性
    • JDK动态代理不需要额外的依赖,是Java标准库的一部分。
    • CGLIB动态代理需要依赖第三方库。
  5. 代理方式
    • JDK动态代理是基于接口的代理。
    • CGLIB动态代理是基于继承的代理。
  6. Final类代理
    • JDK动态代理无法代理final类。
    • CGLIB动态代理也无法代理final类。
  7. 代理对象创建
    • JDK动态代理通过Proxy.newProxyInstance()方法创建代理对象。
    • CGLIB动态代理通过生成目标类的子类来创建代理对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/688561.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu20.04 安装jekyll

首先使根据官方文档安装&#xff1a;Jekyll on Ubuntu | Jekyll • Simple, blog-aware, static sites 如果没有报错&#xff0c;就不用再继续看下去了。 我这边在执行gem install jekyll bundler时报错&#xff0c;所以安装了rvm&#xff0c;安装rvm可以参考这篇文章Ubuntu …

javaweb——socket

定义 Socket&#xff08;套接字&#xff09;是计算机网络编程中的一种抽象&#xff0c;用于在网络上进行通信。它允许计算机之间通过网络进行数据传输。在Java中&#xff0c;Socket类提供了对TCP/IP协议的支持&#xff0c;通过它可以创建客户端和服务端程序&#xff0c;实现网…

FLUENT Meshing Watertight Geometry工作流入门 - 9 生成体网格

本视频中学到的内容&#xff1a; 讨论体网格的重要性&#xff0c;并了解生成体网格的不同方法 了解体网格质量&#xff0c;以及如何改进 视频链接&#xff1a; FLUENT Meshing入门教程-9生成体网格_哔哩哔哩_bilibili 体网格生成是使用大量离散体积或单元来离散化/表示计算模…

跨境云手机如何简化tiktok运营流程

如今&#xff0c;tiktok已经成为世界范围内都非常流行的社交媒体平台。然而在大多数情况下&#xff0c;由于网络原因&#xff0c;tiktok无法在国内使用&#xff0c;但依然有越来越多的人注册tiktok号码、建立tiktok矩阵。原因是tiktok仍然有大量的流量可供商业使用&#xff0c;…

java面试题基础篇

1.java面向对象三大特性 ​ 封装&#xff08;Encapsulation&#xff09;&#xff1a;是面向对象方法的重要原则&#xff0c;就是把对象的属性和操作&#xff08;或服务&#xff09;结合为一个独立的整体&#xff0c;并尽可能隐藏对象的内部实现细节。 ​ 继承&#xff1a;就是…

php 函数(方法)、日期函数、static关键字

php 函数、日期函数 1. php函数2. 日期函数3. static 1. php函数 函数是一段可重复使用的代码块&#xff0c;可以将一系列操作封装起来&#xff0c;使代码更加模块化、可维护和可重用&#xff0c;来大大节省我们的开发时间和代码量&#xff0c;提高编程效率。 <?php// …

Sora OpenAI 101教程(一):从文本生成令人兴奋的视频的 AI 模型

想象一下&#xff0c;您可以通过简单的文本提示创建令人惊叹的视频&#xff0c;例如“一个人带着狗在月球上行走”。听起来不可能&#xff0c;对吧&#xff1f;好吧&#xff0c;现在不再这样了&#xff0c;感谢 OpenAI 的最新人工智能模型 Sora&#xff0c;它可以从文本生成令人…

情人节官宣频发,白敬亭宋轶等多对情侣陷情风。

♥ 为方便您进行讨论和分享&#xff0c;同时也为能带给您不一样的参与感。请您在阅读本文之前&#xff0c;点击一下“关注”&#xff0c;非常感谢您的支持&#xff01; 文 |猴哥聊娱乐 编 辑|徐 婷 校 对|侯欢庭 情人节甜蜜满溢&#xff0c;娱乐圈情侣们争相晒幸福。2024年&…

《苍穹外卖》知识梳理P11-Apache POI导出报表

一.Apache POI 可以通过Apache POI处理excel文件&#xff0c;核心操作是读和写 应用场景 银行网银交易明细各种业务系统导出Excel报表批量导入业务数据 使用步骤 1.导入maven坐标 <dependency><groupId>org.apache.poi</groupId><artifactId>poi&…

安装ts-node有感

起因&#xff1a;想要在vsCode上运行ts脚本 解决方案&#xff1a; 1.安装vsCode插件 code runner 2.全局安装ts-node 这一步遇到三个问题&#xff1a; ①.node版本问题&#xff1a;需安装版本18以上node&#xff0c;可使用nvm去控制不同的node版本 ②.certificate has exp…

NHANES数据库使用(1)

官网&#xff1a;NHANES - National Health and Nutrition Examination Survey Homepagehttps://www.cdc.gov/nchs/nhanes/index.htm 1、打开数据库 2、 选择数据集 B区检索方法和变量。C区检索数据集。A区含有B区和C区的功能。选择 NHANES 2017-March 2020打开。 3、打开数据…

flowpilot Pxiel 6 redmi K30 Pro

Installation flowdriveai/flowpilot Wiki GitHub Flowpilot can be installed on: Android phone Non-rooted running Android 10Android 11Android 12Rooted running Android 13 requires rootDesktop pc with Ubuntu > 20.04. 安装Termux https://f-droid.org/repo…

【Kuiperinfer】笔记01 项目预览与环境配置

学习目标 实现一个深度学习推理框架设计、编写一个计算图实现常见的算子&#xff0c;例如卷积、池化、全连接学会如何进行算子的优化加速使用自己的推理框架推理常见模型&#xff0c;检查结果是否能够和torch对齐 什么是推理框架&#xff1f; 推理框架用于对已经训练完成的模…

【完全二叉树节点数!】【深度优先】【广度优先】Leetcode 222 完全二叉树的节点个数

【完全二叉树】【深度优先】【广度优先】Leetcode 222 完全二叉树的节点个数 :star:解法1 按照完全二叉树解法2 按照普通二叉树&#xff1a;深度优先遍历 后序 左右中解法3 按照普通二叉树&#xff1a;广度优先遍历 层序遍历 ---------------&#x1f388;&#x1f388;题目链接…

Linux进程概念 (下) 地址空间

前言 中篇讲了进程为什么要有优先级&#xff0c;以及环境变量和通过代码获得环境变量 本篇主要讲解什么是地址空间 &#xff0c; 地址空间是怎么设计的&#xff1f;为什么要有地址空间&#xff1f; 程序地址空间 先看下图 验证上图的正文代码至堆的地址是不是从低地址向高地…

python-自动化篇-运维-网络-IP

文章目录 IP自我介绍IPy安装模块windowsLinux IPy介绍支持大多数 IP 地址格式IPv4 地址IPv6 地址网络掩码和前缀 派生网络地址将地址转换为字符串使用多个网络多网络计算方法 IP自我介绍 IP地址规划是网络设计中非常重要的一个环节&#xff0c;规划的好坏会直接影响路由协议算…

红队攻防之office文件钓鱼制作ppt钓鱼

为众人抱薪者&#xff0c;不可使其冻毙于风雪&#xff1b;为自由开路者&#xff0c;不可使其困顿于荆棘。 PPT手势触发 这种攻击则利用的是鼠标轨迹来进行操作&#xff0c;比如鼠标点击、鼠标移动等。 首先&#xff0c;创建一个普通的PPTX文件&#xff0c;随便填入一些内容&…

Shell脚本条件语句

1.条件测试 文件测试与整数测试 test命令 测试表达式是否成立&#xff0c;若成立返回0&#xff0c;不成立返回其他数值 格式1&#xff1a;test 条件表达式 格式2&#xff1a;[ 条件表达式 ] 测试 是否成功使用 $? 操作符&#xff1a; -d&#xff1a;测试是否为目…

【计算机网络】P2P应用

将会在两个例子中得出结果 1&#xff0c;对等文件分发 &#xff1b;2&#xff0c;大型对等方社区中的服务器 P2P文件分发 自拓展性 直接成因是&#xff1a;对等方除了是比特的消费者外还是它们的重新分发者BitTorrent 一个用于文件分发的P2P协议洪流 torrent 参与一个特定文件…

Python——列表

一、列表的特性介绍 列表和字符串⼀样也是序列类型的数据 列表内的元素直接⽤英⽂的逗号隔开&#xff0c;元素是可变的&#xff0c;所以列表是可变的数据类型&#xff0c;⽽字符串不是。 列表的元素可以是 Python 中的任何类型的数据对象。如&#xff1a;字符串、…