探索【注解】、【反射】、【动态代理】,深入掌握高级 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的源码注释中有说明:所有的注解类型都需要继承该公共接口,本质上看注解是接口,但是代码并没有显式声明继承关系,可以直接查看字节码文件;

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

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 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文件

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

// ********** 略 **********  
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());

	@Test
	public 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);
		}
	}

在这里插入图片描述

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

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.结构

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

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

2.静态代理

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

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

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 两个加数的和
	 */
	@Override
	public 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 两个加数的和
	 */
	@Override
	public 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 {

    @Autowired
    private CalculatorImpl calculator;

    /**
     * 代理类交给springboot进行管理,通过注入的方式,传入被代理类,生成代理类  
     * @return 代理类
     */
    @Bean
    public 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 {
	@Resource
	private 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.实现

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

# 案例
	每次保存用户前 对用户信息进行校验,保存完成后返回用户信息
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 用户名
	 */
	@Override
	public 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
	 */
	@Override
	public 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());
	@Autowired
	private UserServiceBiImpl userService;


	@Test
	public 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.实现

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

# 保存用户前对信息进行校验,然后保存完数据,返回用户信息
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 客户名称
	 */
	@Override
	public 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 异常
	 */
	@Override
	public 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
	 */
	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());
	@Autowired
	private ClientServiceImpl clientService;
	@Autowired
	private ClientServiceInterceptor clientServiceInterceptor;


	@Test
	public 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动态代理通过生成目标类的子类来创建代理对象。

你可能感兴趣的:(springboot,spring,boot)