文章目录
- 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)
method
是一个Method
类型的对象,代表了一个方法。getAnnotation(Class<T> annotationClass)
是AnnotatedElement
接口中的一个方法,用于获取特定类型的注解
。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”) 给字段进行赋值
field
是一个Field
类型的对象,代表了一个字段(成员变量)。set()
是Field
类中的一个方法,用于设置特定对象的字段的值。obj
是要操作的对象实例。"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() 通过反射构造对象
studentClass
是一个Class
对象的引用,代表了一个类的定义。getConstructor()
是Class
类中的一个方法,它返回指定类中的公共构造方法,其中不需要传递任何参数。如果你的类中没有明确的定义无参构造方法,这个方法将抛出NoSuchMethodException
异常。- 一旦获得了构造方法,
newInstance()
方法会使用该构造方法创建新的对象实例。 - 返回的
Object
引用obj
就是通过反射机制创建的新对象的引用。
- m.invoke(object, “xxxxxx”) 通过反射执行方法
m
是一个Method
类型的对象,代表了一个方法。invoke()
是Method
类中的一个方法,用于调用特定对象的方法。object
是要调用方法的对象实例。"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.反射优缺点
优点
- 反射提高了Java程序的
灵活性
和扩展性
,降低耦合性
,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类;
缺点
性能问题
:使用反射基本上是一种解释操作,用于字段和方法接入时要慢于直接代码。使用反射会模糊程序内部逻辑
:程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射增加了安全问题
:比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。
代理
代理模式(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.优点
- 简单易懂:静态代理模式相对简单,易于理解和实现,适合初学者学习和使用。
- 编译时类型检查:由于代理类在编译时已经确定,因此可以进行类型检查,避免一些运行时错误。
- 适用于简单场景:对于一些简单的需求,静态代理是一种简洁有效的实现方式,例如日志记录、性能监控等。
2.3.缺点
- 代码重复:静态代理需要为每一个被代理类编写一个代理类,如果代理类逻辑较多,会导致代码冗余,增加维护成本。
- 不灵活:静态代理在编译时确定代理类和被代理类的关系,一旦确定就无法修改,导致扩展性较差。如果需要代理的类过多,静态代理会变得不太实用。
- 不支持横切关注点的动态添加:静态代理的横切关注点在编译时已经确定,无法在运行时动态添加新的横切关注点。
- 不支持异步调用:静态代理是同步的,无法支持异步调用。
3.动态代理
3.1.JDK的动态代理
JDK动态代理是一种在运行时生成代理类的机制,它允许在代理类中动态地处理方法调用。这种代理机制通过
InvocationHandler
接口来实现,代理类在运行时通过实现指定接口生成,并且可以在代理类的方法中插入自定义的逻辑。在这个过程中,代理类并不是在编译时就确定的,而是在运行时动态生成的。
- InvocationHandler(调用处理器):
InvocationHandler
是一个接口,用于定义代理类的具体行为。它包含一个方法invoke
,在代理对象的方法被调用时会被触发。通过实现这个接口,可以在方法调用前后执行自定义的逻辑。 - Proxy(代理类):
Proxy
类是 JDK 提供的用于创建动态代理类和实例的工具类。通过newProxyInstance
方法,可以在运行时生成代理对象。这个方法接收一个类加载器、一组接口和一个InvocationHandler
实现作为参数。 - 动态代理类: 由
Proxy
类在运行时动态生成的代理类。这个类实现了指定的接口,并将方法调用委托给InvocationHandler
的invoke
方法。
JDK动态代理使用步骤:
- 定义接口: 首先定义一个业务接口,该接口将由代理类和实际业务类共同实现。
- 实现业务类: 创建一个实际的业务类,实现定义的业务接口。
- 实现 InvocationHandler: 创建一个实现
InvocationHandler
接口的类,该类将负责在代理对象的方法调用时执行特定逻辑。 - 创建代理对象: 使用
Proxy.newProxyInstance
方法创建代理对象,该方法接收类加载器、接口数组和InvocationHandler
实例作为参数。 - 调用代理对象方法: 通过代理对象调用方法,代理对象将在执行方法前后执行
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动态代理的优点:
- 无需手动编写代理类: 相较于静态代理,JDK动态代理无需手动编写代理类,减少了代码冗余。
- 支持多个接口代理: 代理类可以实现多个接口,灵活性较高。
- 运行时动态生成: 代理类的生成是在运行时进行的,使得系统更加灵活和可扩展。
JDK动态代理的缺点:
- 只能代理接口: JDK动态代理只能代理接口,无法代理类。
- 运行效率相对较低: 由于代理类的生成和方法调用过程都需要运行时处理,相对于静态代理或者CGLib动态代理,JDK动态代理的效率较低。
应用场景:
- AOP(面向切面编程): JDK动态代理常用于实现AOP,通过在方法调用前后添加日志记录、事务管理、性能监控等功能,实现了横切关注点的统一管理。
- Spring框架中的事务管理: Spring框架通过动态代理实现了声明式事务管理,对被@Transactional注解修饰的方法进行代理,从而实现了事务管理的功能。
- 远程方法调用(RPC): JDK动态代理可以在远程方法调用时,通过网络传输代理对象,实现客户端和服务端之间的通信。
- 安全检查: 在方法调用前后进行安全检查,比如权限验证、参数校验等。
- 性能监控: 在方法调用前后记录方法执行时间,进行性能监控和分析。
- 缓存代理: 在方法调用前先检查缓存中是否存在结果,如果存在则直接返回缓存结果,否则执行方法并缓存结果。
3.2.CGLib代理
CGlib(Code Generation Library)是一个强大的,高性能的代码生成类库,它
通过动态生成字节码来创建代理对象
。相对于 JDK 动态代理而言,CGlib 提供了更多的功能和灵活性。
CGLib使用步骤可以包括
- 创建Enhancer对象:首先,我们创建一个Enhancer对象,它是CGLIB库的核心类之一,用于设置代理对象的属性和行为。
- 设置父类:使用
setSuperclass()
方法设置要代理的对象的类。CGLIB通过继承的方式创建代理对象,因此需要指定一个父类。 - 设置拦截器:通过
setCallback()
方法设置代理对象的拦截器。拦截器是一个实现了MethodInterceptor接口的对象,它定义了在方法调用前后进行的额外操作,比如日志记录、性能监控等。 - 创建代理对象:调用Enhancer对象的
create()
方法来创建代理对象。在这一步,CGLIB会动态生成一个代理类,并覆盖父类中的方法来添加拦截器的逻辑。 - 返回代理对象:返回创建的代理对象。这个代理对象实际上是在运行时动态生成的一个子类,它继承自被代理类,并且在方法调用时会先执行拦截器的逻辑,然后再调用被代理类的方法。
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对比
- 实现原理:
- JDK动态代理基于接口,使用Java反射机制实现代理。
- CGLIB动态代理基于继承,通过生成目标类的子类来实现代理。
- 目标类要求:
- JDK动态代理要求目标类必须实现接口。
- CGLIB动态代理可以代理未实现接口的类。
- 性能:
- JDK动态代理的性能相对较低,因为它基于反射调用目标方法。
- CGLIB动态代理的性能相对较高,因为它生成的代理类是目标类的子类,直接调用目标方法。
- 依赖性:
- JDK动态代理不需要额外的依赖,是Java标准库的一部分。
- CGLIB动态代理需要依赖第三方库。
- 代理方式:
- JDK动态代理是基于接口的代理。
- CGLIB动态代理是基于继承的代理。
- Final类代理:
- JDK动态代理无法代理final类。
- CGLIB动态代理也无法代理final类。
- 代理对象创建:
- JDK动态代理通过
Proxy.newProxyInstance()
方法创建代理对象。 - CGLIB动态代理通过生成目标类的子类来创建代理对象。
- JDK动态代理通过