当前位置: 首页 > article >正文

Spring自定义BeanPostProcessor实现bean的代理Java动态代理知识

上文:https://blog.csdn.net/qq_26437925/article/details/145241149 中大致了解了spring aop的代理的实现,其实就是有个BeanPostProcessor代理了bean对象。顺便复习下java代理相关知识

目录

    • 自定义BeanPostProcessor实现aop
    • Java动态代理知识
      • 动态代理的几种实现方式
      • Java基于接口的动态代理
        • 例子代码和输出
        • 为什么一定要有接口

自定义BeanPostProcessor实现aop

bean A:

@Service
public class A {

    public A() {
        System.out.println("A()");
    }

    public void say(){
        System.out.println("say A");
    }
}
  • beanPostProcessor
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;

@Service
public class ABeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("a")) {
            System.out.println("a BeanPostProcessor postProcessAfterInitialization");
            return getProxy(bean);
        }
        return bean;
    }

    public Object getProxy(Object targetObject) {
        Enhancer enhancer = new Enhancer();

        Class<?> superClass = targetObject.getClass();
        enhancer.setSuperclass(superClass);

        MethodInterceptor interceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("cglib before...");
                Object res = methodProxy.invokeSuper(o, objects);
                return res;
            }
        };
        enhancer.setCallback(interceptor);

        Object targetProxy = enhancer.create();
        return targetProxy;
    }
}

基于cglib代理

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
  • config类不用EnableAspectJAutoProxy了
@Configuration
@ComponentScan("com.aop.dependency")
public class ConfigOne {
}
  • 测试类
@Test 
public void test() throws Exception {
    ApplicationContext ctx =
               new AnnotationConfigApplicationContext(ConfigOne.class);

	A a = (A) ctx.getBean("a");
	a.say();

	((AnnotationConfigApplicationContext) ctx).close();
}

测试输出
在这里插入图片描述

方法执行的代理输出正常

因为Cglib是用父类继承,新增了一个完整的class字节码。所以可以看到A()构造函数执行了两次, 一次是spring bean生命周期的实例化,一次则是Cglib创建出代理对象执行的。

Java动态代理知识

动态代理的几种实现方式

java对象的产生流程如下:

       编译                    ClassLoader加载               实例化
       |                           |                       |
       |                           |                       |
       |                           |                       |
       
.java ------> .class(字节码)  ---------------> Class Obj ---------> Class Instance

正因为这个流程,所以在你编写Java代码到运行时具体的Java对象,这个过程可以进行很多操作去改变

常见的动态代理技术如下:

  1. Java Proxy(接口&反射机制,新增一个完整的class字节码:继承Proxy,实现接口类)
  2. CGLib(父类继承,新增一个完整的class字节码
  3. AspectJ(修改现有字节码
  4. JavaAgent(修改现有字节码
  5. Byte Buddy,提供api可以在Java应用程序运行时创建和修改Java类,使用例子:https://doctording.blog.csdn.net/article/details/114787289

Java基于接口的动态代理

例子代码和输出
  • 接口
public interface Go {
    void out();
}

  • 其中一个实现类
public class CarGo implements Go{

    @Override
    public void out() {
        System.out.println("car go");
    }
}
  • 代理handler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

class GoProxyHandler implements InvocationHandler {

    private Go go;
    private Go goProxy;

    public GoProxyHandler(Go go) {
        this.go = go;
        // newProxyInstance方法的三个参数:
        // 1. 用哪个类加载器去加载代理对象
        // 2. 动态代理类需要实现的接口
        // 3. 动态代理方法在执行时,会调用this里面的invoke方法去执行
        this.goProxy = (Go)Proxy.newProxyInstance(
                Go.class.getClassLoader(),
                new Class<?>[] { Go.class },
                this);
    }


    // 实现方法的增强, 对PayService内部的所有方法都能应用该代理方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        System.out.println("before go " + methodName);
        // 这里注意是go,即实际的实现类
        Object rs = method.invoke(go, args);
        System.out.println("after go " + methodName);
        return rs;
    }

    // 返回原接口的代理对象,通过反射方式new出来的:Proxy.newProxyInstance
    public Go getProxy() {
        return goProxy;
    }
}
  • 测试代码
public class ProxyTest {

    public static void main(String[] args) {
        Go go = new CarGo();
        GoProxyHandler goProxyHandler = new GoProxyHandler(go);
        Go goProxy = goProxyHandler.getProxy();
        goProxy.out();

        Class<?> clazz = goProxy.getClass();

        // 输出类的直接超类
        Class<?> superclass = clazz.getSuperclass();
        System.out.println("直接超类: " + superclass.getName());

        // 输出类实现的接口
        Class<?> interfaces[] = clazz.getInterfaces();
        System.out.print("实现的接口: ");
        if (interfaces.length > 0) {
            System.out.println(Arrays.stream(interfaces)
                    .map(Class::getName)
                    .collect(Collectors.joining(", ")));
        } else {
            System.out.println("无");
        }
    }
}

在这里插入图片描述

为什么一定要有接口

直观的来看,可以看到最后的代理类是继承了 java.lang.reflect.Proxy,实现了自己的接口

而java是单继承,可实现多接口的模式。

假如没有接口,而又要改变这个类,则必然要继承这个类,而java动态代理的实现必须要继承java.lang.reflect.Proxy,这就变成多继承了,不允许。

另外接口本身就是一种行为规范,基于接口可以有多种实现类。所以java自身的这种动态代理没有问题。


http://www.kler.cn/a/516936.html

相关文章:

  • 设计模式的艺术-职责链模式
  • JVM面试题解,垃圾回收之“分代回收理论”剖析
  • 【Go面试】工作经验篇 (持续整合)
  • 随机变量的变量替换——归一化流和直方图规定化的数学基础
  • MECD+: 视频推理中事件级因果图推理--VLM长视频因果推理
  • k8s namespace绑定节点
  • 【JVM】OOM
  • python——Django 框架
  • QT QListWidget控件 全面详解
  • 使用LabVIEW的History功能实现队列数据的读取而不清空
  • 在 VS Code 中使用 TypeScript 进行开发和打包的几种方法
  • Vue.js 渐进式增强:如何逐步为传统项目注入活力
  • 【深度学习】微积分
  • 移动端ui库uv-ui实现弹窗式picker选择控件
  • Node.js NativeAddon 构建工具:node-gyp 安装与配置完全指南
  • 【小游戏篇】三子棋游戏
  • Ubuntu18.04 搭建DHCP服务器
  • 08.七种排序算法实现(C语言)
  • Kafka中bin目录下面kafka-run-class.sh脚本中的JAVA_HOME
  • 浅谈APP之历史股票通过echarts绘图
  • 回归预测 | MATLAB基于TCN-BiGRU时间卷积神经网络结合双向门控循环单元多输入单输出回归预测
  • Jenkins pipline怎么设置定时跑脚本
  • js封装vue组件
  • C# OpenCV机器视觉:卡尔曼滤波
  • qt QNetworkRequest详解
  • 【springboot集成knife4j】