zoukankan      html  css  js  c++  java
  • Java反射简单使用--第一次细致阅读底层代码

    1:所写的东西都经过验证,保证正确,环境jdk8,eclipse
    2:在例子中,尽量以生产环境中实际代码为例,那种固定值什么的没什么意义
    问题:
    1:想获取调用方法所需要的参数
    2:参数是以json形式存储,在字符串的字段里
    3: 传入的参数可以与方法的参数顺序不一致
    
    解决方案:
    1:涉及到的技术:反射,json转换
    2:思路:
    (1):先将json转换成对应的bean
    (2):通过反射找到对应方法,获取对应的参数
    (3):通过反射获取参数对应的bean,对应参数名称的值,
    (4):注入
    3:可能存在的问题:参数类型未校验,考虑到时json形式,就未校验
    4:上代码(注意:这里的所有参数都是动态,我这里为了测试/方便看才写的固定值)
    
    //quartz job 核心 记录,对于注入的service方法调用需要配合@postConstruct 进行初始化
    package
    com.sony.sie.hrevaluate.quartz.job; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import org.quartz.impl.JobDetailImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.quartz.QuartzJobBean; import org.springframework.util.StringUtils; import net.sf.json.JSONObject; /** * 动态定时任务Job * * @author linrx1 * */ @PersistJobDataAfterExecution @DisallowConcurrentExecution // 不允许并发执行 public class DynamicQuartzJob extends QuartzJobBean { private static final Logger logger = LoggerFactory.getLogger(DynamicQuartzJob.class); @Autowired private ApplicationContext applicationContext; @Override protected void executeInternal(JobExecutionContext jobexecutioncontext) throws JobExecutionException { // use JobDetailImpl replace JobDetail for get jobName JobDetailImpl jobDetail = (JobDetailImpl) jobexecutioncontext.getJobDetail(); String name = jobDetail.getName(); if (StringUtils.isEmpty(name)) { throw new JobExecutionException("can not find service info, because desription is empty"); } String[] serviceInfo = name.split("\."); // serviceInfo[0] is JOB_NAME_PREFIX String beanName = serviceInfo[1]; // methodName & modelName String methodName = serviceInfo[2]; Object beanNameContext = applicationContext.getBean(beanName); Object modelNameContext = applicationContext.getBean(methodName); // method parameter JobDataMap dataMap = jobDetail.getJobDataMap(); try { Class beanNameCls = beanNameContext.getClass(); Class modelNameCls = modelNameContext.getClass(); Object object = JSONObject.toBean(JSONObject.fromObject(dataMap.get("data")), modelNameCls); Class<?>[] parameterTypes = null; Method[] methods = beanNameCls.getMethods(); Parameter[] invokeMethodParams; // the array size may be not enough for (Method n : methods) { if (methodName.equals(n.getName())) { parameterTypes = n.getParameterTypes(); invokeMethodParams = n.getParameters(); Object[] invokeParam = new Object[invokeMethodParams.length]; for (int i = 0; i < invokeMethodParams.length; i++) { String parameterName = invokeMethodParams[i].getName(); Field field = modelNameCls.getDeclaredField(parameterName); field.setAccessible(true); invokeParam[i] = field.get(object); } Method method = beanNameCls.getMethod(methodName, parameterTypes); method.invoke(beanNameCls.newInstance(), invokeParam); } } logger.info("dynamic invoke {}.{}()", beanNameContext.getClass().getName(), methodName); } catch (Exception e) { logger.error("reflect invoke service method error", e); } } }
    
    
    


    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    
    import org.springframework.stereotype.Service;
    
    import net.sf.json.JSONObject;
    
    @Service
    public class HelloService {
        public void sayHello(String aa, Integer bb) {
            System.out.format("%s %d", aa, bb);
        }
    
        public static class Test {
            String aa;
            Integer bb;
    
            public String getAa() {
                return aa;
            }
    
            public void setAa(String aa) {
                this.aa = aa;
            }
    
            public Integer getBb() {
                return bb;
            }
    
            public void setBb(Integer bb) {
                this.bb = bb;
            }
    
        }
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, NoSuchFieldException {      //json 转换 net.sf.json.JSONObject; String str = "{ " + " "aa": "zhangsan", " + " "bb": 1 " + "}";      //注意实例化 Test test = new Test(); test = (Test) JSONObject.toBean(JSONObject.fromObject(str), Test.class); Class<?> clazz = HelloService.class;      //获取类中的方法 Method[] methods = clazz.getMethods(); Class<?>[] parameterTypes = null; Parameter[] parameters; Object[] obj; for (Method n : methods) { if ("sayHello".equals(n.getName())) { parameterTypes = n.getParameterTypes(); parameters = n.getParameters(); // Class clazzTest = test.getClass(); // Object o1 = clazzTest.newInstance(); for (int i = 0; i < parameters.length; i++) {             //获取方法参数对应的方法名,这里只有jdk1.8及以上支持 String parameterName = parameters[i].getName();             //获取类中的属性 Field field = test.getClass().getDeclaredField(parameterName);             //设置类中私有属性可见 field.setAccessible(true);             //获取属性值 Object obj1 = field.get(test); } } }      //获取目标方法 Method method = clazz.getMethod("sayHello", parameterTypes);       //为目标方法注值,第二个参数是值 method.invoke(clazz.newInstance(), test); } }
     
    5:拓展
    (1):这张图片是常用的反射方法
    
    

    (2)这是一个别人写的例子,还不错
    参考这篇文章:https://www.cnblogs.com/sun1993/p/7828535.html
    import java.lang.reflect.Constructor; import java.lang.reflect.*; /*Class:代表一个字节码文件的对象,每当有类被加载进内存,JVM就会在堆上给 * 该类创建一个代表该类的对象。每个类的Class对象是的。 *Class类没有构造方法,获得类对应的Class方法有3种 *1.:getClass()、2.类、接口.class 、3.Class.forName("类全名"); *比较推荐使用第3种方式,使用前两种方式程序扩展性不好。 * *Class类中定义了许多关于获取类中信息的方法: *1.获得该类的构造方法,属性,方法、实例的方法。包含特定情况的获得 *2.获得该类的父类,实现的接口,该类的类加载器,类名、包名等。 *3.判断该类的具体是接口、类、内部类等 *4.方法中加Declared表示可以获得本类定义的任何方法和属性 * *注意:关于获得到的方法、属性、构造器的具体操作被封装在import java.lang.reflect包里面 *Method:里面最常用的方法invoke(对象,可变参数列表)--调用指定的方法 *Field:get/set;获取和修改属性值 *Constrcutor:使用newInstance(可变参数列表)--调用指定构造方法创建类的实例 *注意:私有的要调用前先去掉访问权限限制setAccssible() * */ public class ReflectionWithClass { public static void main(String[] args) throws Exception { //第一种方式获得Class对象,比较麻烦,要先创建对象,再使用对象调用方法 HelloKitty ht = new HelloKitty(); Class clazz = ht.getClass(); //第二种方式获得Class对象。使用静态的属性创建 Class clazz1 = HelloKitty.class; //使用Class对象的静态方法获得Class对象 Class clazz2 = Class.forName("HelloKitty"); //获得该类的类加载器 ClassLoader c = clazz2.getClassLoader(); System.out.println(c.toString()); Class clazz3 = String.class; System.out.println(clazz3.getClassLoader()); //获得该类的实例 Object obj = clazz2.newInstance(); //获得该类的构造器---公开的,getDeclaredConstructors()--可以获得私有的 Constructor[] con = clazz2.getDeclaredConstructors(); for(Constructor cc:con){ System.out.print(cc + " "); } //获得类的方法 Method[] mm = clazz2.getDeclaredMethods(); for(Method mmm:mm){ System.out.print(mmm + " "); } System.out.println(); //获取特定的方法 Method m = clazz2.getMethod("walk",null); System.out.println(m.toString()); Field[] f = clazz2.getDeclaredFields(); for(Field ff:f){ System.out.print(ff+ " "); } //调用指定的方法---先获取,在调用;注意私有方法先设置访问权限 Method m1 = clazz2.getMethod("walk", null); System.out.println("hahahhha"); m1.invoke(obj,null); //调用指定的构造方法创建类实例;先获取在调用 Constructor cc = clazz2.getConstructor(int.class,String.class); Object o1 = cc.newInstance(12,"blue"); //获取和修改对象的属性值 Field ffs = clazz2.getDeclaredField("age"); ffs.setAccessible(true); ffs.set(obj, 29); Object oo = ffs.get(obj); System.out.println(oo); } } class HelloKitty { private int age; public String color = "pink"; public HelloKitty() {} public HelloKitty(int age) { this.age = age; } public HelloKitty(int age,String color) { this.age = age; this.color = color; System.out.println("okokok"); } public void walk(){ System.out.println("hhhhhhhhhhhhh"); } public void talk(int i){ System.out.println(i + "----------" + age); } }

    6:如果只是应用,网上文章一大堆,用就要知道一些具体的细节,不然 emmmm.....(不引战,懂就好)

    这里说一下核心方法 method.invoke
    
    (1)这是invoke 的入口方法
    /**
         * Invokes the underlying method represented by this {@code Method}
         * object, on the specified object with the specified parameters.
         * Individual parameters are automatically unwrapped to match
         * primitive formal parameters, and both primitive and reference
         * parameters are subject to method invocation conversions as
         * necessary.
         *
         * <p>If the underlying method is static, then the specified {@code obj}
         * argument is ignored. It may be null.
         *
         * <p>If the number of formal parameters required by the underlying method is
         * 0, the supplied {@code args} array may be of length 0 or null.
         *
         * <p>If the underlying method is an instance method, it is invoked
         * using dynamic method lookup as documented in The Java Language
         * Specification, Second Edition, section 15.12.4.4; in particular,
         * overriding based on the runtime type of the target object will occur.
         *
         * <p>If the underlying method is static, the class that declared
         * the method is initialized if it has not already been initialized.
         *
         * <p>If the method completes normally, the value it returns is
         * returned to the caller of invoke; if the value has a primitive
         * type, it is first appropriately wrapped in an object. However,
         * if the value has the type of an array of a primitive type, the
         * elements of the array are <i>not</i> wrapped in objects; in
         * other words, an array of primitive type is returned.  If the
         * underlying method return type is void, the invocation returns
         * null.
         *
         * @param obj  the object the underlying method is invoked from
         * @param args the arguments used for the method call
         * @return the result of dispatching the method represented by
         * this object on {@code obj} with parameters
         * {@code args}
         *
         * @exception IllegalAccessException    if this {@code Method} object
         *              is enforcing Java language access control and the underlying
         *              method is inaccessible.
         * @exception IllegalArgumentException  if the method is an
         *              instance method and the specified object argument
         *              is not an instance of the class or interface
         *              declaring the underlying method (or of a subclass
         *              or implementor thereof); if the number of actual
         *              and formal parameters differ; if an unwrapping
         *              conversion for primitive arguments fails; or if,
         *              after possible unwrapping, a parameter value
         *              cannot be converted to the corresponding formal
         *              parameter type by a method invocation conversion.
         * @exception InvocationTargetException if the underlying method
         *              throws an exception.
         * @exception NullPointerException      if the specified object is null
         *              and the method is an instance method.
         * @exception ExceptionInInitializerError if the initialization
         * provoked by this method fails.
         */
    @CallerSensitive
        public Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException,
               InvocationTargetException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, obj, modifiers);
                }
            }
            MethodAccessor ma = methodAccessor;             // read volatile
            if (ma == null) {
                ma = acquireMethodAccessor();
            }
            return ma.invoke(obj, args);
        }
    
    (2)这段英文就不具体翻译啦,大致意思就是使用这个方法的各种可能情况,可能出现的问题,特别提醒,如果练习英语但是不会翻墙,看不到国外的文章可以看底层代码的注释,一样可以练习英语
    (3)开始正式解读这个方法,首先说一下这注解@CallerSensitive
    这个注解是为了堵住漏洞用的。曾经有黑客通过构造双重反射来提升权限,
    原理是当时反射只检查固定深度的调用者的类,看它有没有特权,
    例如固定看两层的调用者(getCallerClass(2))。如果我的类本来没足够
    权限群访问某些信息,那我就可以通过双重反射去达到目的:反射相关
    的类是有很高权限的,而在 我->反射1->反射2 这样的调用链上,反射2
    检查权限时看到的是反射1的类,这就被欺骗了,导致安全漏洞。
    使用CallerSensitive后,getCallerClass不再用固定深度去寻找
    actual caller(“我”),而是把所有跟反射相关的接口方法都标注上
    CallerSensitive,搜索时凡看到该注解都直接跳过,这样就有效解决了
    前面举例的问题
    思考:反射还有双重反射?有没有多重反射呢?

    (3):invoke方法可以分为俩部分,一部分是访问控制检查,第一个if;第二部分就是MethodAccessor。invoke()实现执行方法

    针对第一部分:简单来说就是访问权限检查,判断你是否可以访问这个方法

    具体来说就是: 检查override,如果override为true,(Method的父类AccessibleObject中声明的变量)跳过检查;否则继续; 快速检查,判断该方法的修饰符modifiers是否为public,如果是跳过检查;否则继续; 详细检查,通过方法的(protected/private/package)修饰符或方法的声明类(例如子类可以访问父类的protected方法)与调用者caller之间的关系,判断caller是否有权限访问该方法。这里推荐看一下java四种访问权限,可能我们一般只用到private和public,但访问权限控制很重要,在一些多人合作的项目中尤为重要 

    这里就涉及到一个有意思的 Field(他父类也是AccessibleObject),他的override是false,导致我们在获取属性是常用setAccessible(true),当属性为private修饰

    针对第二部分MethodAccessor.invoke();也就是真正的调用方法 

    这里有一个注释  // read volatile,这个很有意思是,volatile 是一个类型修饰符。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略。
    //这是methodAccessor,对应的代码
    private volatile MethodAccessor methodAccessor;
    逻辑很简单就是简单的赋值,如果不存在就创建;实现就不太好理解啦,看源码(提示:一定要好好看注释)
    // NOTE that there is no synchronization used here. It is correct
        // (though not efficient) to generate more than one MethodAccessor
        // for a given Method. However, avoiding synchronization will
        // probably make the implementation more scalable.
        private MethodAccessor acquireMethodAccessor() {
            // First check to see if one has been created yet, and take it
            // if so
            MethodAccessor tmp = null;
            if (root != null) tmp = root.getMethodAccessor();
            if (tmp != null) {
                methodAccessor = tmp;
            } else {
                // Otherwise fabricate one and propagate it up to the root
                tmp = reflectionFactory.newMethodAccessor(this);
                setMethodAccessor(tmp);
            }
    
            return tmp;
        }
    
    // Returns MethodAccessor for this Method object, not looking up
        // the chain to the root
        MethodAccessor getMethodAccessor() {
            return methodAccessor;
        }
    
    // Sets the MethodAccessor for this Method object and
        // (recursively) its root
        void setMethodAccessor(MethodAccessor accessor) {
            methodAccessor = accessor;
            // Propagate up
            if (root != null) {
                root.setMethodAccessor(accessor);
            }
        }
    
    //这个是我在看别人文章时看到的,这个方法为了维护root引用,标记一下,日后再来研究
    //这个方法可能会导致类膨胀
    //类膨胀:类的膨胀(Bloating)指的是类中成员过多,甚至出现无序增加的情况。过大的类,会使得复杂度急剧增加,维护会变得更为困难。所以需要控制类的增长。
    //https://www.2cto.com/kf/201506/410716.html,这篇文章讲解类膨胀 Method copy() { Method res = new Method(clazz, name, parameterTypes, returnType, exceptionTypes, modifiers, slot, signature, annotations, parameterAnnotations, annotationDefault); res.root = this; res.methodAccessor = methodAccessor; return res; }

    注意这个方法acquireMethodAccessor,他有一个调用反射工厂的过程,ReflectionFactory利用MethodAccessor的字节码生成类MethodAccessorGenerator直接创建一个代理类,通过间接调用原方法完成invoke()任务;
    这句话有几个概念:字节码,动态代理,反射最终都是调用原始类
    这些遗留问题日后在解决把!!!

    7:总结一下我理解的反射和应用场景

    (1)反射的概念:可以理解成你去了一个小黑屋,里面可能有各种东西,都上了锁,你通过反射就能获取里面的东西,并且去除锁,知道东西的真正面目

    (2)反射应用场景:简单来说就是“动态带有固定”,举个例子,比如我上面的例子,方法是动态的,但是方法的参数是固定的;传入的参数固定的,但是顺序未必和方法一致的; 

  • 相关阅读:
    __proto__、prototype、constructor 之间的关系
    call()与apply()区别
    条件注释判断浏览器
    永远在页面底部的层
    jQuery1.9和jQuery2.0加载(IE10下条件判断失效)
    mousewheel
    2013多校第四场 G题 ZZ的搬砖难题
    hdu 4389 x mod f(x) 数位DP
    hdu 4468 spy 构造kmp
    hdu 4466 triangle 三角形统计 数学计数
  • 原文地址:https://www.cnblogs.com/xiaoshahai/p/11776462.html
Copyright © 2011-2022 走看看