SpringBoot AOP代理问题,Controller...spring内的代理对象重新生成代理对象的问题
基本了解AOP代理
JDK代理 和 CGLib 代理两种。 springboot默认使用JDK动态代理代理对象,如代理对象未存在实现接口类则自动转换为CGLib进行代理< CGLib 代理是通过继承代理对象进行动态代理的,所以代理对象即便未实现接口类也可进行代理。 故CGLib的局限性比JDK小。 底层是通过ASM字节码生成的代理对象>。
Springboot 配置代理方式
JDK 代理 <默认>:
#默认配置
spring:
aop:
proxy-target-class: false
开启CGLib 代理 :
spring:
aop:
proxy-target-class: true
熟悉场景代码
在Controller内针对某个Service进行动态代理进行AOP处理。
#
@Slf4j
@RestController
@Validated
@RequiredArgsConstructor
@RequestMapping("api/node")
public class NodeController extends BaseController {
private final NodeService nodeService;
@PostMapping()
public VideoTronResponse save(Node node) throws VideoTronException {
# 创建CGLib 代理
NodeServiceImpl nodeService1 = (NodeServiceImpl) TrafficAlertMessageProxyFactory.createProxy(nodeService.getClass(), new NodeSaveProxy(nodeService));
nodeService1.saveNode(node);
# 创建JDK 代理
//TrafficAlertMessageProxyFactory.createProxy(nodeService, new NodeSaveProxy(nodeService)).save(node);
//nodeService.saveNode(node);
node.setNodePassword("not viewable");
return new VideoTronResponse().success().data(node).message("添加成功");
}
}
public class TrafficAlertMessageProxyFactory {
/**
* CGlib 代理
* @param clazz
* @param callback
* @return
*/
public static Object createProxy(Class clazz, Callback callback){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(callback);
return enhancer.create();
}
/**
* JDK 代理
* ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h
* @return
*/
public static Object createProxy(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
return Proxy.newProxyInstance(loader, interfaces, h);
}
/**
* JDK 代理
* @param i
* @param h
* @return
* @param <I>
*/
public static <I> I createProxy(I i, InvocationHandler h){
return (I)Proxy.newProxyInstance(i.getClass().getClassLoader(), i.getClass().getInterfaces(), h);
}
public class NodeSaveProxy extends TrafficSend implements MethodInterceptor {
private NodeService nodeService;
private NodeSaveProxy(){
super();
}
public NodeSaveProxy(NodeService nodeService){
this.nodeService = nodeService;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// TODO 保存数据时将数据注册到其他服务
Node node = (Node) objects[0];
String url = String.format(TrafficStatic.CENTRAL_NODE_SAVE_URL, node.getNodeIp(), node.getNodePort());
try{
send(url, node);
methodProxy.invokeSuper(nodeService, objects);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 发送信息到目标地址;
*
* @param url
* @param param
*/
@Override
public <T> Object send(String url, T... param) {
// TODO add request coding
}
}
Service Interface
public interface NodeService extends IService<Node> {
/**
* // TODO 注释
* @param node
*/
void save(Node node);
}
Service 实现; 问题: 需要在save之前将节点注册到另一点服务器; NodeServiceImpl 已继承ServiceImpl<NodeMapper,Node> 此时无法集成抽象类TrafficSend类。 且在保存数据库前需要将数据注册到另一台服务器。
@Service
public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements NodeService {
// 详细代码就不贴了。
@Override
public void save(Node node) {
nodeMapper.insert(node);
}
}
问题和解决
当配置为CGLib动态代理时,且Service使用了AOP注解,例如 事务,日志记录时在Controller内注入的对象就不是原对象而是通过CGLib代理生成的对象。
spring:
aop:
proxy-target-class: false
注入的对象为JDK Proxy对象
spring:
aop:
proxy-target-class: true
注入的对象为CGLib Proxy对象
必须使用 JDK 代理时
使用JDK代理对象时,代理对象不可是CGLib代理对象。否则报
java.lang.ClassCastException: com.sun.proxy.$Proxy$ cannot be cast to
转换异常
此时请将配置修改为JDK代理即可<前提一定是代理对象不是原对象且是CGLib代理生成的Bean,不适用于其他情况>
如果修改配置文件影响其他业务,修改配置文件不理想时,可以使用CGLib代理生成所需对象。
spring:
aop:
proxy-target-class: false
默认注入的对象为JDK Proxy对象
此时即可代理所要代理的对象。
@PostMapping()
public VideoTronResponse save(Node node) throws VideoTronException {
# 创建JDK 代理
TrafficAlertMessageProxyFactory.
createProxy(nodeService, new NodeSaveProxy(nodeService)).
save(node);
nodeService.saveNode(node);
node.setNodePassword("not viewable");
return new VideoTronResponse().success().data(node).message("添加成功");
}
使用 CGLib 代理时
使用CGLib代理对象时,如报错
Superclass has no null constructors but no arguments were given
构造函数异常。 CGLib 动态代理时需要通过构造函数进行代理。
1: 查看是否存在 @RequiredArgsConstructor 注解。 不能使用此注解注入,不然不可通过CGLib代理。
此注解注入的Bean是有参构造函数。 但CGLib代理时代理对象必须存在无参构造函数。 使用@AutoWired注入
2:
spring:
aop:
proxy-target-class: true
默认注入的对象为JDK Proxy对象
@PostMapping()
public VideoTronResponse save(Node node) throws VideoTronException {
# 创建CGLib 代理
NodeServiceImpl nodeServiceProxy = (NodeServiceImpl) TrafficAlertMessageProxyFactory.
createProxy(nodeService.getClass(), new NodeSaveProxy(nodeService));
nodeServiceProxy.saveNode(node);
return new VideoTronResponse().success().data(node).message("添加成功");
}
总结
使用JDK动态代理时代理对象必须存在接口类,代理对象必须为原对象或JDK代理对象。不可是CGLib代理生成的对象。
使用CGLib动态代理时代理对象必须存在无参构造器。