Java reflection mechanism and dynamic proxy

It is said that Java is a dynamic language. Why is it dynamic? Reflection! Through reflection, we can dynamically obtain class instances. We usually write things ourselves, and there is little chance of using reflection, but there are reflections everywhere at the bottom of frameworks such as spring, hibernate, mybatis, etc

When I first learned spring, I came into contact with the following configuration. Spring's control inversion, IOC, and gave the object to spring to create. I vaguely remember that I wrote a lot of XML configuration files at that time. It was very confusing at that time! When reversing the creation right of an object to spring, you need to:

 <bean id="小写类名",class="带包名的全类名">
   <property name="XXX" value="XXX"/>
 </bean>

She's actually helping us create objects through reflection

To get back to business, let's start in the following steps

1. Three ways to obtain class objects

1: Three ways to get class objects

Figure 1

//1 注意,当我用ide 快捷生成 左半部分的时候,Class<?> forName  即便<?>是问号,他也是泛型的
//Class<?> forName = Class.forName("com.atGongDa.entity.person");

//2 然后我们可以这样
// Class<person> personClass = (Class<person>) Class.forName("com.atGongDa.entity.person");

//3 但是,在开发的时候,是不会写上泛型的,因为在开发框架的人们,在写它的时候,是不知道,程序员要传递进来什么类型
Class forName = Class.forName("com.atGongDa.entity.person");

/*
newInstance() 创建对象的实例,注意这里获取的对象,和直接new 出来的对象是不一样的,不知直接调用类里面的方法
调用的是无参的 构造器,这也是为什么要习惯性的写上一个无参的构造器,留着给反射用!!!
*/
Object o = forName.newInstance();
System.out.println(o);

类名.class
对象.getClass()

II Discard new, dynamically build the object of any class, obtain its methods and execute them

 //获取Class对象,它里面它里面封装着类的属性和方法
 Class clazz= Class.forName("com.atGongDa.entity.person");
 // 创建类实例,不过直接使用它没意义(因为我最终的目的还是使用对象里面的方法和字段)
 Object o = clazz.newInstance();

Get the methods in the class (including the methods of its parent class), excluding those that cannot get private

Return value: Method Object array. The method object in it is the abstract entity, class method or instance method (including abstract method) of the method in person above. If you look at its API, you will find that it has a series of methods, such as execution with the method it represents

 Method[] methods = clazz.getmethods();

Get all methods (including private)

Method[] declaredMethods = clazz.getDeclaredMethods();

Gets the specified method

Method method = clazz.getDeclaredMethod("text");

Violent reflex, execution method

    //执行方法: Method对象执行方法,// 两个参数
    //  谁的方法? 创建实例
    //  获取method对象
    // Method对象调用invoke()
    Object o = clazz.newInstance();
    method.setAccessible(true);
   method.invoke(o,null); ///参数就是原方法的参数

III Gets any field of the class

@Test
public void textField() throws NoSuchFieldException,illegalaccessexception {
    //获取所有字段
    Field[] fields = person.class.getDeclaredFields();
    for (Field f:fields) {
        System.out.println("字段=="+f.getName());
    }
    //获取指定名字的字段
    Field field = person.class.getDeclaredField("neme");
    System.out.println(field.getName());

   /* Field neme = person.class.getField("neme");
    System.out.println(neme.getName());*/

   //获取指定对象的Field值
    Object p  = new person("张武",11);
    //暴力反射
    field.setAccessible(true);
    Object o = field.get(p);
    System.out.println(o);

    //给指定对象的 当前字段设置值
    field.set(p,"haha"); //filed 字段是上面的neme
}

IV Tool method

/* @Param obj 对象
* @Param methodName 方法名
* @Param avgs 方法参数,可变参数
*
* 要求,执行执行指定对象的指定方法
* */
@Test
public Object invoke1(Object obj,String methodName,Object ... avgs) throws InvocationTargetException,illegalaccessexception {

    //创建 长度为avgs   类型为Class  的数组,(可变参数可以看作一个数组)
    Class[] classes = new Class[avgs.length];
    //循环 avgs.length 次,把每个入参的Class映射,传递进 Class数组中,得到的这个Class类型数组,就是getDeclaredMethod需要的第二个入参
    for (int i=0;i<avgs.length;i++){
       classes[i] = avgs[i].getClass();
    }
    Class clazz = obj.getClass();
    /*
    @CallerSensitive
    public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
    throws NoSuchMethodException,SecurityException {
     可以看到他的第二个参数是Class类型的,但是具体是啥类型,不知道,所以使用上面啊那个for循环推到
 * */
    try {
        Method method = clazz.getDeclaredMethod(methodName,classes);
        //若 方法是私有的可访问
        method.setAccessible(true);
        return method.invoke(obj,avgs);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }
    return  null;
}

2. Full path with package name + method name + variable parameter

/*
 * 工具方法
 * @Param String objName 对象
 * @Param methodName 方法名
 * @Param avgs 方法参数,可变参数
 *
 * 要求,执行执行指定对象的指定方法
 * */
@Test
public Object invoke2(String objName,Object ... avgs) throws Exception{
    Object o1 = Class.forName(objName).newInstance();
   // return  invoke1(o1,methodName,avgs);
    Class[] classes = new Class[avgs.length];
    for(int i=0;i<avgs.length;i++){
        classes[i]=avgs[i].getClass();
    }
    //获取Method 对象
   Method declaredMethod = o1.getClass().getDeclaredMethod(methodName,classes);
    System.out.println("方法名=="+declaredMethod.getName());
    //暴力反射
    declaredMethod.setAccessible(true);
   return declaredMethod.invoke(o1,avgs);

}
 //在 本类中查找,找不到,去父类中查找
    public Method getmethod(Class clazz,String name,Class... avgs){
        for(;clazz!=Object.class;clazz = clazz.getSuperclass()){
            try {
              return  clazz.getDeclaredMethod(name,avgs);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }       return null;
    }

V Dynamic agent

The purpose of proxy is to enhance the functions of existing class objects. Proxy object = original object + enhancement method

Of course not. If we write this class ourselves and have a complete set of source code in hand, we can change the source code directly. The problem is that in most cases, the class is inherited by us. There is no source code for us to change. Do you want to enhance it? Only agent

Spring AOP's aspect oriented programming uses it incisively and vividly. I don't care how many objects come. Let them stand in line I can cross cut them and give them collective strength

Note:

Let's see how to play with dynamic agents

API

 /**
 * @param   loader the class loader to define the proxy class
 * @param   interfaces the list of interfaces for the proxy class
 *          to implement
 * @param   h the invocation handler to dispatch method invocations to
 */
Proxy.newProxyInstance (ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)  //根据传递进去的被代理类的信息,生成代理对象

example:

Interface

public interface Calcullator {
    public float add(Integer i,Integer j);
    public float sub(Integer i,Integer j);
}

Implementation class

public class CalcullatorImpl implements Calcullator {
    @Override
    public float add(Integer i,Integer j) {
    
        return i+j;
    }
    @Override
    public float sub(Integer i,Integer j) {
        return i*j;
    }
}

agent

@Test
public void lastProxy(){
    Calcullator calcullator = new CalcullatorImpl();
  Calcullator c =  (Calcullator) Proxy.newProxyInstance(calcullator.getClass().getClassLoader(),new Class[]{Calcullator.class},new InvocationHandler() {
            @Override
            public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
                logger.info("开启事务");
                    // 执行方法
                 try {
                     Object result = method.invoke(calcullator,args);
                     logger.info("提交事务");
                     return result;
                  }catch (Exception e){
                     logger.info("回滚事务");
                 }
                 return null;
            }
        });
    System.out.println(c.add(1,2));
}

Focus! There's a pit here

Reason: when we finish executing c.add (1,2), C is our enhanced object. Call add and trigger the callback function. In the next step, we will execute the invoke method of invocationhandler, and its parameter proxy of object type is the object it will eventually return. If we use it here, we will trigger the invoke method again. It is called in the method, and then trigger the invoke. Infinite loop

Vi Generics and reflection

//反射越过泛型检验
@Test
public void text2() throws Exception {
    //ArrayList<Integer>在这个集合中添加一个 String 字符串....
    //思路:泛型只是在编译期才有,但是真正运行起来就被擦出了...反射ArrayList<Integer>   拿到他的字节码文件(运行期)再添加字符串
        ArrayList <Integer> list = new ArrayList<>();
        list.add(111);   //无错
       //list.add("abc");  //报错
        Class clazz = Class.forName("java.util.ArrayList");
        Method add = clazz.getmethod("add",Object.class);
        add.invoke(list,"abc");  //泛型反射
    for (Object a: list) {
        logger.info(a);
    }
}

First, prepare the following JavaBean and Dao of the persistence layer

@lombok
public class person {

    @namePro(name="张三")
    private  String neme;
    private  Integer age;
    public person() {
        System.out.println("无参的构造器");
    }

    public person(String neme,Integer age) {
        System.out.println("有参的构造器");
        this.neme = neme;
        this.age = age;
    }
}

personDao

public class PersonDao extends  Dao<person> { }

Test class

///泛型与反射组合,很多框架经常使用下面这个方法
@Test
public void text1() throws Exception {
    PersonDao personDao = new PersonDao();
    
    //根据id 问dao要 person
    //从数据库中按照 i  把对象的信息找出来,但是这个时候,从数据库里面拿出来是信息仅仅的一些字段的信息,// 我们的要求是:  当service层调用get()方法的时候,我返回给他的是一个包装好的对象,也就是说,我通过反射创建T类型 的对象,然后返回给他
    person p = personDao.get(1);
    logger.info("p=="+p);
}

The get method mainly does two things: 1 Create instance 2 Query the database to add data to the instance object

Create instance using reflection? We know it must be through class Newinstance () method, then the problem comes, 1 First think about whose class we want, person's class, 2 How can I fix it? You can't use t.class, can you? I can't pass the compilation Through observation, we can find that the only place where person appears is the position of the generics of persondao. What we need to do is to take out the generics of Dao and assign them to the class clazz declared in advance. This is always OK

How exactly? You can see the following solution: start in Dao's construction method

Persistent layer

public class Dao<T> {
public static final Logger logger = Logger.getLogger(Dao.class);
//目标就是获取 T  对应的Class 对象,为什么像下面那么写呢? 因为  T.class 不存在

private Class<T> clazz;

public Dao() {
    logger.info("dao 的无参构造器");

    //结果:10:52:37,546  INFO Dao:14 - Dao's Constructor 's  this ==com.atGongDa.entity.PersonDao@56ef9176  全类名
    //为啥是personDao  因为this是当前调用这个函数的对象,而当前对象是我 new  PersonDao()
    logger.info("打印this的全类名 =="+this);

    logger.info("this的 Class对象"+this.getClass());

    //获取Dao子类的父类
    Class clazz1 = this.getClass().getSuperclass();

    logger.info("this的父类的Class对象=="+clazz1);
    //this的父类的Class对象==class com.atGongDa.entity.Dao   结果还是没有T 泛型

    //获取Dao子类带泛型参数的父类,Dao<person>
    Type type = this.getClass().getGenericSuperclass();

    logger.info("type=="+type);  //com.atGongDa.entity.Dao<com.atGongDa.entity.person>
    //注意: Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型
    //我们要使用的是他的子接口  ParameterizedType  里面的
    /*Type[] getActualTypeArguments()返回表示此类型实际类型参数的 Type 对象的数组。
        注意,在某些情况下,返回的数组为空。如果此类型表示嵌套在参数化类型中的非参数化类型,则会发生这种情况。
        返回 :表示此类型的实际类型参数的 Type 对象的数组
    * */

    //获取具体的泛型参数
    if(type instanceof ParameterizedType){     //这是
        ParameterizedType parameterizedType = (ParameterizedType)type;
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        //这有个技巧,假如说我们仅仅想看看数组里面有什么,用工具Arrays.asList()
        logger.info("T=="+ Arrays.asList(actualTypeArguments));//[class com.atGongDa.entity.person]

        if(actualTypeArguments!=null&&actualTypeArguments.length>0){
            Type arg = actualTypeArguments[0];
            logger.info("arg=="+arg);

            if (arg instanceof Class){
                clazz= (Class<T>) arg;
            }
        }
    }
}

 //从数据库中按照 i  把对象的信息找出来,然后返回给他
public T get(Integer i) throws Exception {
    //问题是啥呢?  那么如何得到 Dao<T> T所对象的Class对象
    // T.class   没有
    logger.info("Dao get==>"+clazz);
    
    //创建实例
    T t1 = clazz.newInstance();
    //这里直接手动写属性名,获取它的属性,把查出来的值放进去      
    Field name = clazz.getDeclaredField("neme");
    /**
     *  当然框架的设计者肯定不会这么做,因为它根本不知道现在的clazz是个啥! 
     *  他们获取全部的属性,和结合数据库中查询出来的属性 封装clazz对象里面 (这也是为什么PO的属性名不能乱写,和数据表字段是有关联的)
     *  Field[] declaredFields = clazz.getDeclaredFields();
     */

    name.setAccessible(true);
    name.set(t1,"张三");
    //返回Class对象
    return t1;
}

Another use of class

   Object o = forName.newInstance();
  InputStream resourceAsStream = o.getClass().getClassLoader().getResourceAsStream("123.properties");
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>