Java基础(9)-RTTI反射与三种代理模式小结

文章目录

      • 1.1 RTTI
      • 1.2 Class类的使用
        • 1.获取Class对象
        • 2.通过Class解析类中数据
      • 1.3 反射
        • 1.初始化私有构造器
        • 2.修改私有成员变量
        • 3.执行私有方法
      • 1.4 代理模式
        • 1. 静态代理
        • 2.JDK动态代理
        • 3.CGLIB动态代理
      • Thanks

这两天复习了下反射和代理相关内容,今天总结下~

1.1 RTTI

RTTI指的是Run-Time-Type-Indentification,即运行时类型信息,在Java中有两种实现RTTI的方式:第一种是在编译期就已经确定了类的详情,例如类的名称和其类型和其中的方法,那么可以在编码中直接获取其运行数据;而第二种是在编译期无法知道类的确切的数据和方法等,这时需要使用的是反射技术去动态地解析类的数据

1.2 Class类的使用

首先需要从Class类作为切入点,因为这个类是反射的入口.
Class类位于java.lang包,Class类表示正在运行的Java应用程序中的类和接口,开发者可以通过它获取某些类的信息:

public final class Class
extends Object
implements Serializable, GenericDeclaration, Type, AnnotatedElement

1.获取Class对象

获取Class对象有三种常用方法:

    1. 使用Class.forName(String path)
//绝对路径
Class<?>  clazz = Class.forName("thinking_in_java.RTTI.dynamic_proxy.DynamicProxyFactory")
//相对路径
Class<?>  clazz = Class.forName("DynamicProxyFactory")
  • 使用getClass()
Class<?>  clazz = new DynamicProxyFactory().getClass();
  • 使用class字面量
Class<DynamicProxyFactory>  clazz = DynamicProxyFactory.class;

以上三种方法都可以获取到类的Class,但是有一些不同在于使用forName()方法和getClass()方法时会将类中的静态域初始化,而使用class字面量则不会将静态域初始化:

  • DemoClass
public class DemoClass {
    static {
        System.out.println("静态块初始化");
    }

    public DemoClass() {
        System.out.println("构造方法初始化");
    }
}

@Test
    public void fun() throws ClassNotFoundException {
        System.out.println("使用forName获取Class对象:");
        Class.forName("thinking_in_java.RTTI.class_and_reflect.DemoClass");
        
        //System.out.println("使用getClass获取Class对象:");
        //Class aClass = new DemoClass().getClass();
        
       // System.out.println("使用class字面量获取Class对象");
       // Class demoClassClass = DemoClass.class;
    }

输出结果:

使用forName获取Class对象:
静态块初始化

使用getClass获取Class对象:
静态块初始化
构造方法初始化

使用class字面量获取Class对象

2.通过Class解析类中数据

这部分比较简单,可以看看接口文档,熟悉下常用的方法即可

1.3 反射

Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种在运行时动态地获取信息 以及动态调用对象的方法 的功能称为Java的反射机制.

首先设置一个DemoClass来作为接下来演示是操作类:

  • DemoClass
/**
 * @Auther: ARong
 * @Date: 19-8-31 上午1:07
 * @Description: 演示反射
 **/
public class DemoClass {

    private String name = "ARong Before Reflect";
    private int age;
    private final static String FINAL_FLAG = "BEFORE";

    static {
        System.out.println("静态块初始化");
    }

    private DemoClass(String name, int age) {
        System.out.println("含参构造方法初始化");
        this.name = name;
        this.age = age;
    }

    public DemoClass() {
        System.out.println("构造方法初始化");
    }

    private String showMeesage(String name, int age) {
        return name+"同学的年龄是"+age;
    }

  public String toString() {
        return "DemoClass{" +
                "name='" + this.name + '\'' +
                ", age=" + this.age +
                '}';
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
    
    public String getFlag() {
        return FINAL_FLAG;
    }
}

1.初始化私有构造器

通过反射机制,我们可以在程序运行时突破类构造器的私有限制,从而去创建一个对象,下面演示使用反射获取私有含参构造函数并且将其创建:

@Test
    public void fun1() throws Exception {
        // TODO: 19-8-31 通过反射执行私有方法[空构造方法为private]
        Class<DemoClass> clazz = DemoClass.class;
        //获取类的特点含参构造
        Constructor<DemoClass> constructor = clazz.getDeclaredConstructor(String.class, int.class);
        //破除权限
        constructor.setAccessible(true);
        //通过构造函数创建对象实例
        DemoClass demoClass = constructor.newInstance("阿荣通过反射获取到私有构造函数初始化对象", 20);
        System.out.println(demoClass.toString());
    }

输出

静态块初始化
含参构造方法初始化
DemoClass{name=‘阿荣通过反射获取到私有构造函数初始化对象’, age=20}

2.修改私有成员变量

通过反射机制,我们可以在程序运行时获取并且修改程序的私有成员变量:

@Test
    public void fun2() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        // TODO: 19-8-31 私用反射修改私有成员变量
        DemoClass demoClass = new DemoClass();
        Class<? extends DemoClass> clazz = demoClass.getClass();
        System.out.println("未修改前的name:"+demoClass.getName());
        //获取私有变量name
        Field name = clazz.getDeclaredField("name");
        //破除权限
        name.setAccessible(true);
        //修改
        name.set(demoClass, "阿荣通过反射修改私有变量");
        System.out.println("修改后的name:"+demoClass.getName());
    }

输出

静态块初始化
构造方法初始化
未修改前的name:ARong Before Reflect
修改后的name:阿荣通过反射修改私有变量

既然反射机制那么地万能,所以我们是否也可以修改final修饰的成员变量呢?试试就知道:

@Test
    public void fun3() throws Exception {
        // TODO: 19-8-31 通过反射修改私有的final变量
        //使用反射获取类信息
        DemoClass demoClass = new DemoClass();
        Class<? extends DemoClass> clazz = demoClass.getClass();
        //获取私有变量
        Field field = clazz.getDeclaredField("FINAL_FLAG");
        //设置访问权限
        field.setAccessible(true);
        System.out.println("使用反射修改前:"+demoClass.getFlag());
        //修改私有变量
        field.set(demoClass, "AFTER");
        System.out.println("使用反射修改后:"+demoClass.getFlag());
    }

输出

静态块初始化
构造方法初始化
使用反射修改前:BEFORE
使用反射修改后:BEFORE

事实证明,我们无法通过反射去修改对象中的final对象,因为在JVM编译类成字节码时,就已经将其优化成

public String getFlag() {
        return "BEFORE";
}

3.执行私有方法

同样地,我们可以通过反射去执行类中的私有方法:

@Test
    public void fun4() throws Exception {
        // TODO: 19-8-31 通过反射执行私有方法[空构造方法为private]
        DemoClass demoClass = new DemoClass();
        Class<? extends DemoClass> clazz = demoClass.getClass();
        //获取私有方法并且设置参数
        Method showMessage = clazz.getDeclaredMethod("showMeesage", String.class, int.class);
        //突破权限
        showMessage.setAccessible(true);
        //执行方法
        String message = (String) showMessage.invoke(demoClass, "阿荣通过反射执行私有含参方法", 20);
        System.out.println(message);
    }

输出

静态块初始化
构造方法初始化
阿荣通过反射执行私有含参方法同学的年龄是20

1.4 代理模式

代理模式是Java中一种应用很广泛的设计模式,它的思想是通过代理对象访问到目标对象,并且对目标对象进行增强,这里的目标对象即为被代理的对象

代理模式可分为两种 ,即静态代理和动态代理;对于静态代理而言,代理对象必须在编译期知道目标对象的具体类型和方法;当在编译期无法判断目标对象的具体类型时,则需要使用动态代理技术,而动态代理一般有JDK动态代理与CGLIB动态代理两种,下面来一一介绍

1. 静态代理

静态代理需要代理对象和目标对象同时实现一个提供方法的接口,并且在代理对象中运用组合的手法:
Java基础(9)-RTTI反射与三种代理模式小结_第1张图片

  • ShopService接口
	public interface ShopService {
    /**
    * @auther: Arong
    * @description: 查找商品类别信息
    * @param: type
    * @return: Object
    * @date: 下午12:27 19-8-30
    */
    Object showProducts(String type);
}
  • ShopService实现类
	/**
 * @Auther: ARong
 * @Date: 19-8-30 下午12:27
 * @Description: ShopService的实现类(目标对象)
 **/
public class ShopServiceImp implements ShopService{
    @Override
    public Object showProducts(String type) {
        //根据type查询数据库
        System.out.println("ShopServiceImp查询数据库,获取"+type+"的数据");
        return type;
    }
}
  • 代理对象
/**
 * @Auther: ARong
 * @Date: 19-8-30 下午12:32
 * @Description:  ShopService的代理实现类
 **/
public class ShopServiceProxy implements ShopService{
    private ShopService target;

    /**
     * @auther: Arong
     * @description: 代理构造方法
     * @param: [target]
     * @return:
     * @date: 下午12:34 19-8-30
     */
    public ShopServiceProxy(ShopService target) {
        this.target = target;
    }


    @Override
    public Object showProducts(String type) {
        //增强方法
        System.out.println("静态代理对参数进行了正确性判断......");
        Object res = target.showProducts(type);
        System.out.println("静态代理对结果进行了处理......");
        return res;
    }
}

以上三个类组成了一个完整的静态代理模式链,有方法接口,目标对象和代理对象,我们在程序中如何使用代理对象来执行目标对象的方法或者增强呢?其实很简单,如下:

public class Test {
    @org.junit.Test
    public void fun() {
        //使用动态代理执行ShopService的showProducts方法
        ShopServiceProxy proxy = new ShopServiceProxy(new ShopServiceImp());
        proxy.showProducts("APPLE");
    }
}

输出

静态代理对参数进行了正确性判断…
ShopServiceImp查询数据库,获取APPLE的数据
静态代理对结果进行了处理…

我们从输出可以看到,通过代理对象执行了目标对象的方法并且对其进行了增强;但是这种静态代理的方式对于需要代理的目标来说如果很多的话,需要为其一一创建一个特定的代理类,这样十分地不方便,所以这就需要在运行时动态地获取到目标对象并且对其增强,此时产生了动态代理技术;

2.JDK动态代理

JDK动态代理技术使用的是java.lang.reflect包,即反射包中的工具类,通过与反射技术是结合,可以让我们在运行时动态地去执行目标对象的方法,我们使用到的目标对象还是之前的ShopServiceImp:

  • java.lang.reflect Proxy,主要方法为
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
static Object    newProxyInstance(ClassLoader loader,  //指定当前目标对象使用类加载器
 Class<?>[] interfaces,    //目标对象实现的接口的类型
 InvocationHandler h      //事件处理器
) 
  • java.lang.reflect InvocationHandler,主要方法为
 Object    invoke(Object proxy, Method method, Object[] args) 
// 在代理实例上处理方法调用并返回结果。
  • JDK动态代理对象
/**
 * @Auther: ARong
 * @Date: 19-8-30 下午5:30
 * @Description: 动态代理工厂
 **/
public class DynamicProxyFactory{
    //被执行的目标对象,不限定类型
    private Object target;

    public DynamicProxyFactory(Object target) {
        this.target = target;
    }
    /**
     * @auther: Arong
     * @description: 获取目标对象的代理对象
     * @param: []
     * @return: java.lang.Object
     * @date: 下午4:18 19-8-31
     */
    public Object getProxyInstance() {
        //创建处理器,对目标对象的方法进行增强
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //增强方法
                System.out.println("静态代理对参数进行了正确性判断......");
                //执行目标对象的方法
                Object res = method.invoke(target, args);
                System.out.println("静态代理对结果进行了处理......");
                return res;
            }
        };
        //获取代理对象
        Object res = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),//获取目标对象的类加载器
                target.getClass().getInterfaces(),//获取目标对象实现的接口
                handler                           //处理器
        );
        return res;
    }
}

对JDK动态代理进行测试:

public class Test {
    @org.junit.Test
    public void fun() {
    	//获得目标对象的动态代理工程
        DynamicProxyFactory factory = new DynamicProxyFactory(new ShopServiceImp());
        //获得目标对象的动态代理对象
        ShopService proxy = (ShopService)factory.getProxyInstance();
        //打印当前代理对象类位置
        System.out.println(proxy.getClass());
        //使用代理对象执行增强方法
        String res = (String) proxy.showProducts("APPLE");
        System.out.println(res);
    }
}

输出

class com.sun.proxy.$Proxy4
静态代理对参数进行了正确性判断…
ShopServiceImp查询数据库,获取APPLE的数据
静态代理对结果进行了处理…
APPLE

我们可以从代码中发现,现在我们的类图是这样子的:
Java基础(9)-RTTI反射与三种代理模式小结_第2张图片

此时动态代理对象(工厂)已经不再需要实现ShopService接口,所以它无需在编译期去通过组合辨别目标对象的类型,而是在运行时通过反射机制去获取目标对象的字Class与实现的接口和方法参数,这样就更有利于动态代理对象与接口解耦

3.CGLIB动态代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

  • CGLIB与JDK动态代理的不同之处
    1.JDK动态代理需要目标对象实现接口取得方法,而CGLIB动态代理则不需要目标对象去实现接口

  • 2.CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)(当目标对象有实现方法接口时,Spring使用JDK动态代理;当没有实现接口时,使用CGLIB代理)

  • 3.CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类

CGLIB与JDK动态代理最大的区别就是:

使用动态代理的对象必须实现一个或多个接口
使用cglib代理的对象则无需实现接口,达到代理类无侵入。并且使用CGLIB生成的动态代理对象是目标对象的增强子类

  • 引入Jar包
    使用CGLIB需要引入CGLIB的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了CGLIB。

cglib的Maven坐标

<dependency>
    <groupId>cglibgroupId>
    <artifactId>cglibartifactId>
    <version>3.2.5version>
dependency>
  • ShopService无需实现接口
/**
 * @Auther: ARong
 * @Date: 19-8-30 下午12:27
 * @Description:
 **/
public class ShopService {
    public Object showProducts(String type) {
        //根据type查询数据库
        System.out.println("ShopService查询数据库,获取"+type+"的数据");
        return type;
    }
}
  • CGLIB动态代理
/**
 * @Auther: ARong
 * @Date: 19-8-30 下午10:46
 * @Description: cglib动态代理生成目标代理子类
 **/
public class DynamicProxyFactory implements MethodInterceptor {
    //需要被代理的对象
    private Object target;

    public DynamicProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        //获取增强器
        Enhancer enhancer = new Enhancer();
        //设置增强器的父类
        enhancer.setSuperclass(target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建增强代理子类
        return enhancer.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //增强方法
        System.out.println("静态代理对参数进行了正确性判断......");
        //执行目标对象的方法
        Object res = method.invoke(target, args);
        System.out.println("静态代理对结果进行了处理......");
        return res;
    }
}
  • 测试CGLIB动态代理
public class Test {
    @org.junit.Test
    public void fun() {
        //获取CGLIB动态代理工厂
        DynamicProxyFactory proxyFactory = new DynamicProxyFactory(new ShopService());
        //获取动态代理子类对象
        ShopService proxy = (ShopService)proxyFactory.getProxyInstance();
        //打印代理对象信息
        System.out.println(proxy.getClass());
        String res = (String) proxy.showProducts("APPLE");
        System.out.println(res);
    }
}

输出

class thinking_in_java.RTTI.cglib_proxy.ShopService E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIB$bf82dc7d
静态代理对参数进行了正确性判断…
ShopService查询数据库,获取APPLE的数据
静态代理对结果进行了处理…
APPLE

ok,看了两天的反射和代理终于总结完了,其实还有很多内容可以深入了解,例如Mybatis的动态代理实现和Spring的动态代理实现等,这些就留着下次深入学习吧!~(゜-゜)つロ (干杯)

Thanks

  • https://segmentfault.com/a/1190000011291179
  • https://juejin.im/post/5ad3e6b36fb9a028ba1fee6a
  • http://www.matools.com/api/java8
  • https://blog.csdn.net/yhl_jxy/article/details/80635012
  • https://juejin.im/post/598ea9116fb9a03c335a99a4#heading-0
  • https://blog.csdn.net/qq_21808961/article/details/80376732

你可能感兴趣的:(Java,RTTI,Java反射的使用,Java三种代理模式,JDK代理,CGLIB代理,-----【Think,In,Java】)