【无标题】

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动态代理时代理对象必须存在无参构造器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值