java底层 —反射、动态代理

java底层 —反射、动态代理和注解

在这里插入图片描述

1. 反射介绍

1.1 反射

反射是指程序可以访问,检测,修改它本身状态或行为的一种能力。

1.2 java的反射机制

java的反射机制是指在程序运行状态中,给定任意一个类,都可以获取到这个类的属性和方法;给定任意一个对象都可以调用这个对象的属性和方法,这种动态的获取类的信息和调用对象的方法的功能称之为java的反射机制。 一言以蔽之:反射机制可以让你在程序运行时,拿到任意一个类的属性和方法并调用它。

1.3 java反射的主要功能

  • 运行时构造一个类的对象;
  • 运行时获取一个类所具有的的成员变量和方法;
  • 运行时调用任意一个对象的方法;
  • 生成动态代理; 其实反射最主要的功能我觉得是与框架搭配使用。

1.4 java类类型

想要理解反射首先需要知道Class这个类,它的全称是java.lang.Class类。java是面向对象的语言,讲究万物皆对象,即使强大到一个类,它依然是另一个类(Class类)的对象,换句话说,普通类是Class类的对象,即Class是所有类的类(There is a class named Class)。 对于普通的对象,我们一般会这样创建:

    Code code1 = new Code();

上面说了,所有的类都是Class的对象,那么如何表示呢,可不可以通过如下方式呢:

    Class c = new Class();

但是我们查看Class的源码时,是这样写的:

    private Class(ClassLoader loader) { 
        classLoader = loader; 
    }

可以看到构造器是私有的,只有JVM才可以调用这个构造函数创建Class的对象,因此不可以像普通类一样new一个Class对象,虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Class对象,共有三种方式,如下:

    Class c1 = Test.class; 这说明任何一个类都有一个隐含的静态成员变量class,这种方式是通过获取类的静态成员变量class得到的
    Class c2 = test.getClass(); test是Test类的一个对象,这种方式是通过一个类的对象的getClass()方法获得的 
    Class c3 = Class.forName("com.catchu.me.reflect.Test"); 这种方法是Class类调用forName方法,通过一个类的全量限定名获得

这里,c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Test的类类型(class type)。 这里就让人奇怪了,前面不是说Test是Class的对象吗,而c1、c2、c3也是Class的对象,那么Test和c1、c2、c3不就一样了吗?为什么还叫Test什么类类型?这里不要纠结于它们是否相同,只要理解类类型是干什么的就好了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。 示例代码:

public class Test {

    public static void main(String[] args) throws ClassNotFoundException {
        Class<Test> class1 = Test.class;
        System.out.println("类名1:"+class1.getName());

        Test Test = new Test();
        Class<? extends Test> class2 = Test.getClass();
        System.out.println("类名2:"+class2.getName());

        Class<?> class3 = Class.forName("com.catchu.me.reflect.Test");
        System.out.println("类名3:"+class3.getName());
        if(class1==class2){
            System.out.println("class1==class2");
        }
        if(class1==class3){
            System.out.println("class1==class3");
        }
    }
}

输出结果:

    类名1:com.catchu.me.reflect.Test
    类名2:com.catchu.me.reflect.Test
    类名3:com.catchu.me.reflect.Test
    class1==class2
    class1==class3

2.反射的操作

java的反射操作主要是用到了java.lang.Class类和java.lang.reflect反射包下的类,上面说到我们已经可以拿到一个类的Class信息,根据这个Class我们就可以使用某些方法来操作(获取)类的以下信息:

2.1操作构造函数

万物皆对象,类的构造函数是java.lang.reflect.Constructor类的对象,通过Class的下列方法可以获取构造函数对象:

    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  获得该类所有的构造器,不包括其父类的构造器
    public Constructor<T> getConstructor(Class<?>... parameterTypes) // 获得该类所有public构造器,包括父类
    
    //具体
    Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的所有声明构造函数 
    Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数 
    Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数(局部变量是一个字符串类型的) 
    Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数

测试代码如下:

public class TestConstructor {
    public static void main(String[] args) throws Exception{
        Class<?> personClass = Class.forName("com.catchu.me.reflect.Person");
        //获取所有的构造函数,包括私有的,不包括父类的
        Constructor<?>[] allConstructors = personClass.getDeclaredConstructors();
        //获取所有公有的构造函数,包括父类的
        Constructor<?>[] publicConstructors = personClass.getConstructors();
        System.out.println("遍历之后的构造函数:");
        for(Constructor c1 : allConstructors){
            System.out.println(c1);
        }

        Constructor<?> c2 = personClass.getDeclaredConstructor(String.class);
        c2.setAccessible(true); //设置是否可访问,因为该构造器是private的,所以要手动设置允许访问,如果构造器是public的就不用设置
        Object person = c2.newInstance("刘俊重");   //使用反射创建Person类的对象,并传入参数
        System.out.println(person.toString());
    }
}

Person类如下,为测出效果包含一个私有构造函数:

public class Person {
    private int age;
    private String name;
    public Person() {
    }
    private Person(String name){
        this.name = name;
    }
    public Person(int age,String name){
        this.age = age;
        this.name = name;
    }
	//省略set/get方法
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

测试结果如下:

    遍历之后的构造函数:
    public com.catchu.me.reflect.Person(int,java.lang.String)
    private com.catchu.me.reflect.Person(java.lang.String)
    public com.catchu.me.reflect.Person()
    Person{age=0, name='刘俊重'}

由上面可以看到我们在获得某个类的Class类类型之后,可以通过反射包中的方法获取到这个类的构造函数,进而可以创建该类的对象。

2.2操作成员变量

万物皆对象,类的成员变量是java.lang.reflect.Field类的对象,通过Class类的以下方法可以获取某个类的成员变量,值得一提的是变量是包含两部分的,变量类型和变量名:

    public Field getDeclaredField(String name) // 获得该类自身声明的所有变量,不包括其父类的变量
    public Field getField(String name) // 获得该类自所有的public成员变量,包括其父类变量
    
    //具体实现
    Field[] allFields = class1.getDeclaredFields();//获取class对象的所有属性 
    Field[] publicFields = class1.getFields();//获取class对象的public属性 
    Field ageField = class1.getDeclaredField("age");//获取class指定属性 
    Field desField = class1.getField("des");//获取class指定的public属性

示例代码如下:

public class TestField {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;
        //获取所有的成员变量,包含私有的
        Field[] allFields = personClass.getDeclaredFields();
        //获取所有公有的成员变量,包含父类的
        Field[] publicFields = personClass.getFields();
        System.out.println("所有的成员变量:");
        for(Field f : allFields){
            System.out.println(f);
        }
        //获取某个变量的值
        //创建对象的实例
        Constructor<Person> c = personClass.getDeclaredConstructor(String.class);
        c.setAccessible(true); //因为该构造函数时私有的,需要在这里设置成可访问的
        Person person = c.newInstance("刘俊重");
        //获取变量name对象
        Field field = personClass.getDeclaredField("name");
        field.setAccessible(true); //因为变量name是私有的,需要在这里设置成可访问的
        //注意对比下面这两行,官方对field.get(Object obj)方法的解释是返回对象obj字段field的值
        Object value = field.get(person);
        //String name = person.getName();
        System.out.println("获取的变量的值是:"+value);
    }
}

输出结果如下:

    所有的成员变量:
    private int com.catchu.me.reflect.Person.age
    private java.lang.String com.catchu.me.reflect.Person.name
    获取的变量的值是:刘俊重

这里要注意field.get(person)方法,我们根据对象获取属性的常规方法是通过:String name = person.getName(),反射中可以通过:字段.get(对象),这也是获取对象的某个字段,有点类似于invoke方法。

2.3操作成员方法

万物皆对象,类的成员方法是java.lang.reflect.Method的对象,通过java.lang.Class类的以下方法可以获取到类的成员方法,通过方法类Method提供的一些方法,又可以调用获取到的成员方法。

    public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到该类所有的方法,不包括父类的 
    public Method getMethod(String name, Class<?>... parameterTypes) // 得到该类所有的public方法,包括父类的
    
    //具体使用
    Method[] methods = class1.getDeclaredMethods();//获取class对象的所有声明方法 
    Method[] allMethods = class1.getMethods();//获取class对象的所有public方法 包括父类的方法 
    Method method = class1.getMethod("info", String.class);//返回此class1对应的public修饰的方法名是info的,包含一个String类型变量的方法
    Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回此Class对象对应类的、带指定形参列表的方法

测试代码如下:

public class TestMethod {
    public static void main(String[] args) throws Exception {
        Person person = new Person();
        Class<? extends Person> personClass = person.getClass();
        Method[] allMethods = personClass.getDeclaredMethods();
        Method[] publicMethods = personClass.getMethods();
        System.out.println("遍历所有的方法:");
        for(Method m : allMethods){
            System.out.println(m);
        }

        //下面是测试通过反射调用函数
        //通过反射创建实例对象,默认调无参构造函数
        Person person2 = personClass.newInstance();
        //获取要调用的方法,要调用study方法,包含int和String参数,注意int和Integer在这有区别
        Method method = personClass.getMethod("study", int.class, String.class);
        Object o = method.invoke(person2, 18, "刘俊重");
    }
}

测试结果:

    遍历所有的方法:
    public java.lang.String com.catchu.me.reflect.Person.toString()
    public java.lang.String com.catchu.me.reflect.Person.getName()
    public void com.catchu.me.reflect.Person.setName(java.lang.String)
    public void com.catchu.me.reflect.Person.study(int,java.lang.String)
    public int com.catchu.me.reflect.Person.getAge()
    public void com.catchu.me.reflect.Person.setAge(int)
    我叫刘俊重,我今年18,我在学习反射

注意:Object o = method.invoke(person2, 18, “刘俊重”);就是调用person2对象的method方法,格式是:方法名.invoke(对象,参数),类似于获取成员变量值时的get方法。 由上面可以看出反射的强大:通过反射我们可以获取到类类型,通过Class类型我们可以获取到构造函数,进而实例化new出一个对象;通过反射我们可以获取到成员变量和成员方法,通过实例出的对象又可以获取到这些成员变量的值或调用成员方法。这才只是反射的一部分,通过反射我们还可以判断类,变量,方法,是否包含某些特定注解,还可以通过反射来动态代理去调用其它方法,跟注解和动态代理挂起勾会有无限的想象空间,比如spring框架,底层就是通过这些原理。下面在说几个反射常用的API,最后会介绍反射跟注解和动态代理的结合使用。

2.4其它方法

1. 注解中常用的方法:

    Annotation[] annotations = (Annotation[]) class1.getAnnotations();//获取class对象的所有注解 
    Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//获取class对象指定注解 
    Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的 
    Type Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的所有接口的type集合

2. 获取Class对象其它信息的方法:

    boolean isPrimitive = class1.isPrimitive();//判断是否是基础类型 
    boolean isArray = class1.isArray();//判断是否是集合类
    boolean isAnnotation = class1.isAnnotation();//判断是否是注解类 
    boolean isInterface = class1.isInterface();//判断是否是接口类 
    boolean isEnum = class1.isEnum();//判断是否是枚举类 
    boolean isAnonymousClass = class1.isAnonymousClass();//判断是否是匿名内部类 
    boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰 
    String className = class1.getName();//获取class名字 包含包名路径 
    Package aPackage = class1.getPackage();//获取class的包信息 
    String simpleName = class1.getSimpleName();//获取class类名 
    int modifiers = class1.getModifiers();//获取class访问权限 
    Class<?>[] declaredClasses = class1.getDeclaredClasses();//内部类 
    Class<?> declaringClass = class1.getDeclaringClass();//外部类
    ClassLoader ClassLoader = class1.getClassLoader() 返回类加载器
    
    getSuperclass():获取某类所有的父类  
    getInterfaces():获取某类所有实现的接口

3.动态代理

代理的操作是通过java.lang.reflect.Proxy类中实现的,通过Proxy的newProxyInstance()方法可以创建一个代理对象,如下:

public static Object newProxyInstance(ClassLoader loader,<?>[] interfaces,InvocationHandler h)

不要看到这里面一大坨晦涩的屎代码就害怕,这里面是有技巧的,其实都是模板,需要什么,我们传什么过去就可以了。可以看到需要三个参数,类加载器,接口和调用处理者。我们在上面已经能拿到Class类了,使用class.getClassLoader就可以获取类加载器,使用class.getgetInterfaces()可以获取所有的接口,那现在要写的不就是新建一个InvocationHandler对象了吗?事实上,我们动态代理的核心代码也就是在这里面写的。我上面说的模板,其实就是下面这几步:

  1. 书写代理类和代理方法,在代理方法中实现代理Proxy.newProxyInstance();
  2. 代理中需要的参数分别为:被代理的类的类加载器class.getClassLoader(),被代理类的所有实现接口new Class[] { Interface.class },句柄方法new InvocationHandler();
  3. 在句柄方法中重写invoke方法,invoke方法的输入有3个参数Object proxy(代理类对象), Method method(被代理类的方法),Object[] args(被代理类方法的传入参数),在这个方法中,我们可以定制化的写我们的业务;
  4. 获取代理类,强转成被代理的接口;
  5. 最后,我们可以像没被代理一样,调用接口的任何方法,方法被调用后,方法名和参数列表将被传入代理类的invoke方法中,进行新业务的逻辑流程。 看下面的示例代码: 接口PersonInterface:
public interface PersonInterface {
    void doSomething();
    void saySomething();
}

接口的实现类:

public class PersonImpl implements PersonInterface {
    @Override
    public void doSomething() {
        System.out.println("人类在做事");
    }
    @Override
    public void saySomething() {
        System.out.println("人类在说话");
    }
}

代理类:

/**
 * @author 刘俊重
 */
public class PersonProxy {
    public static void main(String[] args) {
        final PersonImpl person = new PersonImpl();
        PersonInterface proxyPerson = (PersonInterface) Proxy.newProxyInstance(PersonImpl.class.getClassLoader(),
                PersonImpl.class.getInterfaces(), new InvocationHandler() {
                    //在下面的invoke方法里面写我们的业务
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if(method.getName()=="doSomething"){
                            person.doSomething();
                            System.out.println("通过常规方法调用了实现类");
                        }else{
                            method.invoke(person,args);
                            System.out.println("通过反射机制调用了实现类");
                        }
                        return null;
                    }
                });
        proxyPerson.doSomething();
        proxyPerson.saySomething();
    }
}

执行结果如下:

人类在做事
通过常规方法调用了实现类
人类在说话
通过反射机制调用了实现类

在我们通过proxyPerson.doSomething()调用的时候,其实不是立马进入实现类的doSomething方法,而是带着方法名,参数进入到了我们的代理方法invoke里面,在这里面我进行了一次判断,如果等于"doSomething"就使用常规方法调用,否则使用反射的方法调用。这样看似还是平时的调用,但是每次执行都要走我们的代理方法里面,我们可以在这里面做些“手脚”,加入我们的业务处理。 可以看下面另一个示例,比如天猫一件衣服正常卖50,现在你是我的vip用户,可以给你打折扣10块,其它业务都是相同的,只有这里便宜了10,重新服务提供者就很麻烦,用代理可以解决这个问题。 接口SaleService:

public interface SaleService {
    //根据尺码返回衣服的大小
    int clothes(String size);
}

接口实现类SaleServiceImpl:

public class SaleServiceImpl implements SaleService {
    @Override
    public int clothes(String size) {
        System.out.println("衣服大小"+size);
        //模拟从数据库取衣服价格
        return 50;
    }
}

普通无折扣的调用测试:

/**
 * @author 刘俊重
 * @Description 普通用户
 */
public class OrdinaryCustom {
    public static void main(String[] args) {
        SaleService saleService = new SaleServiceImpl();
        int money = saleService.clothes("XXl");
        System.out.println("价格是:"+money);

    }
}

输出结果:

衣服大小XXl
价格是:50

代理类ProxySale:

/**
 * @author 刘俊重
 * @Description 代理类
 */
public class ProxySale {
    //对接口方法进行代理
    public static <T> T getProxy(final int discount, final Class<SaleServiceImpl> implementClasses, Class<SaleService> interfaceClasses){
        return (T)Proxy.newProxyInstance(implementClasses.getClassLoader(),
                implementClasses.getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //调用原始对象的方法,获取未打折之前的价格
                        int price = (int) method.invoke(implementClasses.newInstance(), args);
                        return price-discount;
                    }

                });
    }
}

vip用户测试类VipCustom:

/**
 * @author 刘俊重
 * @Description Vip用户,有打折优惠
 */
public class VipCustom {
    public static void main(String[] args) {
        //vip用户,打10元折扣
        int discount = 10;
        SaleService saleService = ProxySale.getProxy(discount, SaleServiceImpl.class, SaleService.class);
        int money = saleService.clothes("xxl");
        System.out.println("价格是:"+money);
    }
}

输出结果是:

衣服大小xxl
价格是:40

可以看到,在未修改服务提供者的情况下,我们在代理类里面做了手脚,结果符合预期。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值