目录
一、实例问题
1、现象
2、原因
3、解决
二、Spring的代理模式
1、静态代理(Static Proxy)
1)原理
2)优缺点
3)代码实现
2、JDK动态代理(JDK Dynamic Proxy)
1)原理
2)优缺点
3)代码实现
3、cglib 代理(Code Generation Library Proxy)
1)原理
2)优缺点
3)代码实现
一、实例问题
1、现象
业务场景中出现过使用内部类获取属性的时候获取不到,导致业务逻辑执行异常,观察代码后发现原因是最底层有@Async的方法导致Spring代理方式变更,特此梳理一下。
如下代码结果展示
类代码
/**
* @author YY-帆S
* @Date 2024/4/16 21:16
*/
@Service
@Slf4j
public class TestProxy {
@Value("${test.boolean:true}")
public boolean aBoolean;
public void testAQuery() {
log.info("testAQuery:{}", aBoolean);
TestProxy bean = SpringContextUtils.getBean(TestProxy.class);
bean.testBQuery();
bean.testCQuery();
}
public void testBQuery() {
log.info("testBQuery:{}", aBoolean);
}
private void testCQuery() {
log.info("testCQuery:{}", aBoolean);
}
@Component
public class InnerClass {
public void testInnerQuery() {
log.info("testInnerQuery:{}", aBoolean);
}
}
//第一次 不加上
// @Async
//第二次加上
@Async
public void testAsync() {
}
}
调用代码
@Resource
TestProxy testProxy;
@Resource
TestProxy.InnerClass innerClass;
@GetMapping("testQuery")
public CommonResult<Object> testQuery() {
testProxy.testAQuery();
innerClass.testInnerQuery();
return new CommonResult<>();
}
不加上@Asnyc的结果
加上@Async的结果
2、原因
加Async之前的bean属性,内部aBoolean=true
加Async之后的bean,内部aBoolean=false,(默认值)
可以观察到,加@Async或@Transcational 这类的注解时,会导致spring切换对类切换代理方式,为cglib的代理模式,翻了spring源码的话会发现,spring生成代理对象的时候使用了Objenesis来创建,Objenesis可以绕过构造方法以及相关的初始化来创建对象,所以生成的代理类中所有的属性全部都是空的。
参考文档:
spring——事务动态代理造成属性为null_切面生成的cglb代理类里面的属性为null-CSDN博客
3、解决
1)减少直接读写属性,而是调用其中的方法
//类代码
@Service
@Slf4j
@Data
public class TestProxy {
@Value("${test.boolean.b:true}")
public Boolean bBoolean;
@Async
public void testAsync() {
}
}
//……调用代码
@Resource
TestProxy testProxy;
@GetMapping("testQuery")
public CommonResult<Object> testQuery() {
log.info("user func:{}", testProxy.getBBoolean());
log.info("direct use: {}", testProxy.bBoolean);
return new CommonResult<>();
}
结果:使用方法获取属性则正常返回配置后的结果true,直接使用属性的为null 空
2)多自测,兄弟们,必现的case,测一下就知道了
二、Spring的代理模式
1、静态代理(Static Proxy)
1)原理
静态代理在编译时确定代理类,代理类和目标类都实现相同的接口。代理类在调用目标类的方法之前或之后添加一些额外的操作。
2)优缺点
优点
- 简单直观:实现简单,易于理解和调试。
- 编译时检查:代理类在编译时确定,可以进行类型检查。
缺点
- 代码冗余:每个接口需要对应一个代理类,导致代码量增加。
- 不灵活:代理逻辑在编译时确定,无法在运行时动态改变。
- 维护困难:如果接口方法增加或修改,代理类需要同步修改。
3)代码实现
public interface UserService {
void addUser(String name);
}
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("Adding user: " + name);
}
}
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public UserServiceProxy(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void addUser(String name) {
System.out.println("Before adding user");
userService.addUser(name);
System.out.println("After adding user");
}
}
// 使用代理
public class Main {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy(userService);
proxy.addUser("Alice");
}
}
2、JDK动态代理(JDK Dynamic Proxy)
1)原理
JDK动态代理基于Java的反射机制,通过在运行时生成代理类来实现。代理类必须实现一个或多个接口。JDK动态代理使用java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来创建代理实例。
2)优缺点
优点
- 接口支持:适用于代理实现了接口的类。
- 轻量级:相比于CGLIB,JDK动态代理生成的代理类较轻量。
- 无需依赖第三方库:基于JDK自带的反射机制实现。
缺点
- 只能代理接口:目标类必须实现接口,如果没有接口则无法使用。
- 性能较低:反射机制相对较慢,性能不如CGLIB。
3)代码实现
public interface UserService {
void addUser(String name);
}
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("Adding user: " + name);
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserServiceProxy implements InvocationHandler {
private Object target;
public UserServiceProxy(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method");
Object result = method.invoke(target, args);
System.out.println("After method");
return result;
}
}
// 使用代理
UserService target = new UserServiceImpl();
UserService proxy = (UserService) new UserServiceProxy(target).getProxy();
proxy.addUser("Alice");
3、cglib 代理(Code Generation Library Proxy)
1)原理
CGLIB代理通过生成目标类的子类并覆盖其方法来实现代理。它使用字节码增强技术。
2)优缺点
优点
- 无需接口:可以代理没有实现接口的类。
- 性能较高:通常情况下,CGLIB代理的性能比JDK动态代理高。
缺点
- 依赖第三方库:需要CGLIB库(Spring中已经包含)。
- 无法代理
final
类和方法:由于CGLIB通过生成子类来实现代理,final
类和方法无法被代理。 - 内存开销大:生成子类会占用更多内存
3)代码实现
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProductService {
public void addProduct(String name) {
System.out.println("Adding product: " + name);
}
}
public class ProductServiceProxy implements MethodInterceptor {
private Object target;
public ProductServiceProxy(Object target) {
this.target = target;
}
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method");
return result;
}
}
// 使用代理
ProductService target = new ProductService();
ProductService proxy = (ProductService) new ProductServiceProxy(target).getProxy();
proxy.addProduct("Laptop");
总结:
- JDK动态代理:适用于目标类实现接口的情况,轻量但性能较低,无法代理没有实现接口的类。
- CGLIB代理:适用于没有实现接口的类,性能较高但依赖第三方库,无法代理
final
类和方法。 - 静态代理:实现简单但不灵活,代码冗余,适用于代理逻辑固定的情况。
根据具体需求和应用场景,可以选择合适的代理模式。Spring默认选择JDK动态代理,如果目标类没有实现接口,则使用CGLIB代理。