欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JDK、CGLib、Javassist实现动态代理

程序员文章站 2022-06-17 20:25:23
...

一、类加载

1.类加载过程模拟(先明白类加载过程,方可模拟类运行期间加载-创建代理类,调用目标方法)

public class Programmer {
    public void code() {
        System.out.println("I'm a Programmer,Just Coding.....");
    }
}
/**
 * 自定义一个类加载器,用于将字节码转换为class对象 
 */
public class MyClassLoader extends ClassLoader {
    public Class<?> defineMyClass(byte[] b, int off, int len) {
        //TODO SOURCE CODE
        return super.defineClass(null,b, off, len);
    }
}
public class MyTest {
    public static void main(String[] args) throws IOException {
        //读取本地的class文件内的字节码,转换成字节码数组  
        File file = new File(".");
        InputStream input = new FileInputStream(file.getCanonicalPath() +
                "\\target\\classes\\com\\max\\dproxy\\loadseq\\Programmer.class");
        byte[] result = new byte[1024];//字节型

        int count = input.read(result);
        // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象  
        MyClassLoader loader = new MyClassLoader();
        Class clazz = loader.defineMyClass(result, 0, count);
        //测试加载是否成功,打印class 对象的名称  
        System.out.println(clazz.getCanonicalName());
        
        try {
            //实例化一个Programmer对象  
            Object o = clazz.newInstance();
            //调用Programmer的code方法  
            clazz.getMethod("code", null).invoke(o, null);
        } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

JDK、CGLib、Javassist实现动态代理

2.类加载过程

JDK、CGLib、Javassist实现动态代理

注:绿色椭圆内即JVM虚拟机状态,具体不做赘述。

二、动态代理

代理模式:

JDK、CGLib、Javassist实现动态代理

静态代理:手动编写代理类代理目标类方法。缺点:手动创建;代理类越来越多,系统规模增大,不易维护;

动态代理:由于JVM通过字节码的二进制(byte-code)信息加载类的,那么,如果我们在运行期系统中,1.遵循Java编译系统组织.class文件的格式和结构2.生成相应的二进制数据3.然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。

JDK、CGLib、Javassist实现动态代理

JDK实现动态代理

public interface Vehicle {
    void drive();
}
public interface Rechargable {
    void recharge();
}
public class ElectricCar implements Rechargable, Vehicle {
    @Override
    public void drive() {
        System.out.println("Electric Car is Moving silently...");
    }
    @Override
    public void recharge() {
        System.out.println("Electric Car is Recharging...");
    }
}
public class InvocationHandlerImpl implements InvocationHandler {
    private ElectricCar car;
    public InvocationHandlerImpl(ElectricCar car) {
        this.car = car;
    }
    @Override
    public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
        System.out.println("You are going to invoke " + paramMethod.getName() + " ...");
        paramMethod.invoke(car, null);
        System.out.println(paramMethod.getName() + " invocation Has Been finished...");
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
        ElectricCar car = new ElectricCar();
        // 1.获取对应的ClassLoader  
        ClassLoader classLoader = car.getClass().getClassLoader();
        // 2.获取ElectricCar 所实现的所有接口
        Class[] interfaces = car.getClass().getInterfaces();
        // 3.设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用  
        InvocationHandler handler = new InvocationHandlerImpl(car);
        /* 
          4.根据上面提供的信息,创建代理对象 在这个过程中,  
          a.JDK会通过根据传入的参数信息动态地在内存中创建和.class文件等同的字节码 
          b.然后根据相应的字节码转换成对应的class,  
          c.然后调用newInstance()创建实例 
         */
        Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
        Vehicle vehicle = (Vehicle) o;
        vehicle.drive();
        Rechargable rechargeable = (Rechargable) o;
        rechargeable.recharge();
    }
}

JDK、CGLib、Javassist实现动态代理

newProxyInstance过程做了3件事:

1.通过传入的类信息(加载器、接口、处理器-增强方法)动态的在内存中创建和.class文件同等的字节码(代理类字节码)

2.将该字节码转换成对应类(生成代理类的步骤)

3.通过newInstance创建类,而后调用1中传入的所有接口方法。

对比类的加载过程,123步骤。

cglib实现动态代理

public class Programmer {
    public void code() {
        System.out.println("I'm a Programmer,Just Coding.....");
    }
}
/*
 * 实现了方法拦截器接口 Spring AOP实现方式
 */
public class Hacker implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");
        proxy.invokeSuper(obj, args);
        System.out.println("****  Oh,what a poor programmer.....");
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
        Programmer progammer = new Programmer();
        Hacker hacker = new Hacker();

        //cglib 中加强器,用来创建动态代理  
        Enhancer enhancer = new Enhancer();
        //设置要创建动态代理的类  
        enhancer.setSuperclass(progammer.getClass());
        // 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截  
        enhancer.setCallback(hacker);
        Programmer proxy = (Programmer) enhancer.create();
        proxy.code();
    }
}

JDK、CGLib、Javassist实现动态代理

javassist实现动态代理

public class Programmer {
    public void code() {System.out.println("I'm a Programmer,Just Coding.....");
    }
}
public class MyGenerator {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        //创建Programmer类       
        CtClass cc = pool.makeClass("com.max.dproxy.javassist.Programmer");
        //定义code方法  
        CtMethod method = CtNewMethod.make("public void code(){}", cc);
        //插入方法代码  增强
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
        cc.addMethod(method);

        //保存生成的字节码  
        cc.writeFile("d://temp");
    }
}

以上先简单显示一下javassist的用法。(其实insertBefore就已经是在针对code方法的增强,上述代码补充上cc字节码的load和创建过程【如下下例中的】及完整的显示了javassist创建代理的步骤)。

//获取动态生成的class  
        Class c = cc.toClass();
        //获取构造器  
        Constructor constructor = c.getConstructor(TicketServiceImpl.class);
        //通过构造器实例化  
        TicketService o = (TicketService) constructor.newInstance(new TicketServiceImpl());
        o.sellTicket();

以下显示使用javassist实现动态代理的完整步骤

public interface TicketService {
    //售票
    void sellTicket();
    //问询
    void inquire();
    //退票
    void withdraw();
}
public class TicketServiceImpl implements TicketService {
    @Override  
    public void sellTicket() {  
        System.out.println("\n\t售票.....\n");  
    }  
    @Override  
    public void inquire() {  
        System.out.println("\n\t问询。。。。\n");  
    }  
    @Override  
    public void withdraw() {  
        System.out.println("\n\t退票......\n");  
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        createProxy();
    }
    /* 
     * 手动创建字节码 
     */
    private static void createProxy() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("com.max.dproxy.staticproxy.StationProxy");
        
        //设置接口  
        CtClass interface1 = pool.get("com.max.dproxy.staticproxy.TicketService");
        cc.setInterfaces(new CtClass[] { interface1 });

        //设置Field  
        CtField field = CtField.make("private com.max.dproxy.staticproxy.TicketServiceImpl station;", cc);
        cc.addField(field);

        CtClass stationClass = pool.get("com.max.dproxy.staticproxy.TicketServiceImpl");
        CtClass[] arrays = new CtClass[] { stationClass };
        CtConstructor ctc = CtNewConstructor.make(arrays, null, CtNewConstructor.PASS_NONE, null, null, cc);
        //设置构造函数内部信息  
        ctc.setBody("{this.station=$1;}");
        cc.addConstructor(ctc);

        //创建5个方法
        //创建收取手续方法
        CtMethod takeHandlingFee = CtMethod.make("private void takeHandlingFee() {}", cc);
        takeHandlingFee.setBody("System.out.println(\"收取手续费,打印发票。。。。。\");");
        cc.addMethod(takeHandlingFee);

        //创建showAlertInfo方法  
        CtMethod showInfo = CtMethod.make("private void showAlertInfo(String info) {}", cc);
        showInfo.setBody("System.out.println($1);");
        cc.addMethod(showInfo);

        //创建sellTicket
        CtMethod sellTicket = CtMethod.make("public void sellTicket(){}", cc);
        //"{this.showAlertInfo 都是在调用这些方法,括号中传入需print的string
        //但proxy对象中,sellTicket方法并未执行,而是执行的动态代理中method的流程
        sellTicket.setBody("{this.showAlertInfo(\"showAlertInfo********\");" + "station.sellTicket();"
                + "this.takeHandlingFee();" + "this.showAlertInfo(\"××××BYBY!××××\");}");
        cc.addMethod(sellTicket);

        //创建inquire方法
        CtMethod inquire = CtMethod.make("public void inquire() {}", cc);
        inquire.setBody("{this.showAlertInfo(\"××××欢迎光临本代售点,问询服务不会收取任何费用,本问询信息仅供参考,具体信息以车站真实数据为准!××××\");"
                + "station.inquire();" + "this.showAlertInfo(\"××××欢迎您的光临,再见!××××\");}");
        cc.addMethod(inquire);

        //创建widthraw方法
        CtMethod withdraw = CtMethod.make("public void withdraw() {}", cc);
        withdraw.setBody("{this.showAlertInfo(\"××××欢迎光临本代售点,退票除了扣除票额的20%外,本代理处额外加收2元手续费!××××\");"
                + "station.withdraw();" + "this.takeHandlingFee();}");
        cc.addMethod(withdraw);

        //获取动态生成的class  
        Class c = cc.toClass();
        //获取构造器  
        Constructor constructor = c.getConstructor(TicketServiceImpl.class);
        //通过构造器实例化  
        TicketService o = (TicketService) constructor.newInstance(new TicketServiceImpl());
        o.sellTicket();
    }
}

JDK、CGLib、Javassist实现动态代理

这里的Proxy代理类即可删除,动态生成代理,取代以下的代理类创建。

public class StationProxy implements TicketService {
    private TicketServiceImpl station;
    public StationProxy(TicketServiceImpl station) {
        this.station = station;
    }
    @Override
    public void sellTicket() {
        // 1.做真正业务前,提示信息
        this.showAlertInfo("××××showAlertInfo before sellTicket××××");
        // 2.调用真实业务逻辑
        station.sellTicket();
        // 3.后处理
        this.takeHandlingFee();
        this.showAlertInfo("××××sellTicket over××××\n");
    }
    @Override
    public void inquire() {
        // 1做真正业务前,提示信息
        this.showAlertInfo("××××showAlertInfo before inquire××××");
        // 2.调用真实逻辑  
        station.inquire();
        // 3。后处理  
        this.showAlertInfo("××××inquire over××××\n");
    }
    @Override
    public void withdraw() {
        // 1。真正业务前处理
        this.showAlertInfo("××××howAlertInfo before withdraw××××");
        // 2.调用真正业务逻辑  
        station.withdraw();
        // 3.后处理  
        this.takeHandlingFee();
    }
//    TODO 除了实现public接口,代理类新增private方法,在test中仍添加进method中
    /* 
     * 展示额外信息 
     */
    private void showAlertInfo(String info) {
        System.out.println(info);
    }
    /* 
     * 收取手续费 
     */
    private void takeHandlingFee() {
        System.out.println("takeHandlingFee......\n");
    }
}

三、总结

1.类的加载:
.java编译形成.class bytecode;而后将byte code通过类加载期load到运行期中进行解释、编译、运行
2.动态代理
实现原理:在运行期模拟类的加载过程,将增强类生成字节码,加载转成类(代理类)
jdk:基于接口?从何说起?因为在newPorxy创建代理类时,传入被代理类的所有接口。
    InvocationHandler作用?
1)自定义的方法处理器,在invoke方法中加入被代理类的增强逻辑。
2)通过InvocationHandler统一管理器,在调用接口方法(drive、recharge时)进行拦截,统一调用invoke方法,根据invoke中传入的method参数,确定调用被代理类的具体方法。
缺点:被代理类与代理类都继承自同一接口,无法实现随机*组合增强。
cglib:两个无关类,增强类只需实现MethodInterceptor,通过enhancer的setCallback,调用intercept增强方法。intercept写法与jdk类似。
javassist:采用类的包名加载类,编译形成字节码,设置接口、字段信息,添加构造方法、接口方法,最后使用构造器实例化类,调用接口方法。