Java反射机制、原理、应用介绍

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jtracydy/article/details/76061026

以前也看过反射,但是并没有体会到精髓,甚至连应用都费劲,只不过知道反射是什么,眼睛就懒汉,需要好汉才能加深理解。现在把反射结合了代理模式等其他的应用以后,对反射有了一些了解,如有问题希望大家不吝赐教,因为这篇博客我以前是转载的,自己打算重新写下,可能不能一次更新完,希望多多体谅。

反射的概述
  • 定义:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
  • 核心:java中的任何一个类在运行时,在类运行之前.java文件通过都会javac命令编译成一个.class文件。某个类可以对应多个对象,但是只能存在一个.class文件,类的对象会在堆中会被分配多个内存,但是只有一个.class文件。.java文件中的代码,通过编译和某个固定的生成.class文件的框架生成JVM能操作的.class文件,反射就是通过操作.class文件来实现对类的操作。但是并不是在什么时候用这个反射都是比较好的,也要有一个固定的场景,比如说只需要创建某个已知类的对象,调用它的某个方法,那就不需要反射了,用了反射反而会降低效率。在Spring中的反射用的比较多,无论是最早的xml配置文件形式的,还是现在用的比较多注解形式。
反射获取类的对象的几种方式:

在下面的应用的介绍中,我并不会介绍所有应用方法,我会把api地址的粘贴出来,提供参考。例如在通过反射获取public方法和private方法时,我只会介绍获取public的。在获取和单个方法和全部类的所有方法时,我只介绍调用单个方法。
这里写图片描述

getDeclaredMethods方法是获取所有访问权限修饰符类型的方法,返回类型是一个Method数组,getMethod方法获取public类型的方法,返回类型是Method。如果你想调用除了public外的方法那么你就用getDeclaredMethods方法,如果你想知道这个类中都有那个方法,直接遍历数组就可以了,如果你想用某个方法,就用getDeclaredMethod方法。


        User user2 = new User();
        Class cu = user2.getClass();
        Class c2 = User.class;
        Class c3 = null;
        try {
            c3 = Class.forName("com.demo.w.reflect.User");
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        }
        System.out.println(c2 == c3);

c3这种获取class对象的方式比较多一些,通常知道了类的名字,可以直接初始化对象了,并不是需要反射。在spring的xml文件中,配置类的地址,可以通过类的地址反射出该类的信息,调用类中的方法等操作,这个应用会在稍后详细说一下的。

反射的构造器获取,调用某个构造器
  • 获取构造器的目的:调用某个构造方法初始化对象。
  • 类型:Constructor
  • 获取构造器的方法:getDeclaredConstructor
  • 初始化对象的方法:newInstance

这里写图片描述

在所有的构造方法中,根据参数类型调用某个构造方法。如果一个类中有多个构造方法,但是它们的参数所有都不相同,要么参数类型不同,要么个数不相同,也就是当前类中重载了多个构造方法,反射也是如此,根据参数的类型选择构造方法。

  • code
    //User类中的属性和构造方法
    public String userName;
    public String pwd;

    public User(){
    }

    public User(String userName,float f,Employee e){
    }
    public User(int u1,int u2,int userName){
    }
    public User(float userName,int uu){
    }
    private User(double u){
    }
    private User(String userName,String pwd){
        this.userName = userName;
        this.pwd = pwd;
    }
//调用某个构造方法初始化类
        Class c3 = null;
        try {
            c3 = Class.forName("com.demo.w.reflect.User");
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        }
        System.out.println("con...............");
        Constructor con3 = c3.getDeclaredConstructor(String.class,String.class);
        con3.setAccessible(true);
        User o3 = (User)con3.newInstance("lucas","pwd");
        System.out.println(o3.getUserName());

获取User类中参数类型为String,String的构造方法,因为此方法的访问权限修饰符是private,所以需要设置该方法允许访问,输出userName属性值,查看结果确实是lucas。

反射的方法获取,调用某个方法
  • 获取方法的目的:调用某个方法
  • 类型:Method
  • 获取方法的方法:getMethod
  • 调用方法的方法:invoke
    这里写图片描述
  • code:
        //代码是承接上面的
        Method method = c3.getMethod("getMethod",String.class);
        method.invoke(o3,(Object) "paramater");
        //method.invoke(c3,(Object) "paramater");

这个有点意思,刚开始我没有注意到这个,在调用invoke方法时,我把invoke的对象换成了.class文件对象,结果悲剧了报错了。
解释下API:Method带着特定的参数,调用潜在的方法。invoke方法的参数Object对象才是真正调用方法的对象,Object对象是通过反射调用构造器获得的,而Class的对象是获取当前类中属性、方法和注解用的,调用法某个方法需要类的对象的,在这里想调用User类中的getMethod方法,需要得到类的对象,方法的参数类型,然后才可以调用方法。
调用方法时要注意,方法不光有访问权限符修饰,还有静态和非静态方法区分,静态方法属于类,所以invoke参数可以为空,method.invoke(null,”paramater”),
调用main方法的代码:

        Class zlass = Class.forName("com.demo.w.reflect.MainClass");
        Object o = zlass.newInstance();
        Method method = zlass.getMethod("main",String[].class);
        String [] str = new String[]{"1","2","3"};
        method.invoke(o,(Object) str);
        //method.invoke(null,(Object) str);

无论是通过反射还是正常调用某个类的方法和属性都需要该类的对象。

反射属性的获取,设置属性值
  • 获取属性的目的:设置属性值
  • 类型:Field
  • 获取属性的方法:getField
  • 设置属性的方法:set
  • code
   c3.getField("pwd").set(o3,"return");
   System.out.println(o3.getPwd());

获得属性值和调用方法基本上异曲同工。

反射获取注解
目前现在用的spring注解偏多一些,这类我只留下了方法,会在应用说一说获得注解的应用。
        Class zlass = Class.forName("com.demo.w.reflect.Employee");
        Annotation [] annotations =zlass.getDeclaredAnnotations();
        for(int i = 0;i < annotations.length;i++){
            System.out.println(annotations[i].annotationType());
        }
反射的应用

反射的应用真的挺多,说两个跟spring相关的,反射在代理模式中的应用我会在后台的文章中更新,现在就说下反射在IOC中和BeanFactory中的应用。想了一下还是决定把两种spring管理Bean对象的方式说说。
对于spring容器我的理解并不到位,在tomcat服务器上,spring容器创建bean实例,管理它的生命周期,维护Bean之间的关系。

在Spring的整个上下文有个集合Map来管理存储spring上下文的Bean,把它定义成container吧,结构Map String Object。

  • . xml形式:先看配置

<?xml version="1.0" encoding="UTF-8"?>  
<beans>  
<bean id="courseDao" 
class="com.qcjy.learning.Dao.impl.CourseDaoImpl">  
  <bean 
  id="courseService" class="com.qcjy.learning.service.impl.CourseServiceImpl">  
         <!-- 控制调用setCourseDao()方法,将容器中的courseDao bean作为传入参数 -->  
         <property name="courseDao" ref="courseDao"></property>  
    </bean>  

</beans> 
        //set方法注入
        private CourseDao courseDao;  
        //一定要写被注入对象的set方法  
        public void setCourseDao(CourseDao courseDao) {  
        this.courseDao = courseDao;  
    }  

看bean标签,存在一个这样的标签就会初始化bean,存在到Map容器中。Spring容器启动时会读取xml的配置,通过class.forName()初始化该类的.class对象,配置中提供了class的路径,然后通过调用构造器去初始化类该类的对象存入container容器中,
map.put("courseDao","newInstance()") 这就是IOC,将创建对象的方式交给spring容器,解耦效果非常的明显,类与类之间的耦合性特别的低,任何bean初始化方式的改变都不会影响注入该属性的类的改变。
依赖注入:看标签property,ref属性是指要注入bean的id,它是唯一的,也就是Map中的key,程序在运行运行时,通过container.get("key") 获取注入的Bean。上面代码的注入方式是set方法依赖注入,通过set方法将bean注入到另外一个类中,在这里通过反射的getMethod方法实现对方法的调用,进而实现对bean的注入。
用到反射的地方:通过反射加载class文件、调用newInstance方法初始化对象存入container容器、通过调用getMethod实现依赖注入。

  • 注解形式:
//我从代码中随便找的一个类,并没有针对性,关注实现过程就好
@Service
public class TeacherServiceImpl implements TeacherService{

     @Autowired
     private TeacherRepositoryImpl teacherRepositoryImpl;

     @Autowired
     private TeacherRepository teacherRepository;
}

@Controller
@RequestMapping("/teacher")
public class TeacherController {

    @Autowired
    private TeacherService teacherService;
}

先说注解,@Controller和@Service用来标识该类是bean,需要被初始化成对象,同时可以被注入到其他的的实体中,@Autowired标识被注入的类。
Spring容器启动时,会扫描整个上下文中的注解,遇到@Controller和@Service这种需要被初始成bean的注解,将会初始化将该类,然后存入到container容器中,默认bean的名字就是该类首字母小写其他的字母不变。
我理解:其实注解就是一种标识,扫描所有的注解,然后可以读取到注解所在的类,创建该.class文件的对象,利用反射读取类中的所有注解,该初始化就初始化。在读取到实现依赖注入标签时,在容器中查找该名字的bean,注入即可。

git:https://github.com/jtracydy/SpringBootTestDemo/tree/master/src/main/java/com/demo/w/reflect
API:http://tool.oschina.net/apidocs/apidoc?api=jdk_7u4
参考文章:
http://blog.csdn.net/sinat_38259539/article/details/71799078
http://blog.csdn.net/mlc1218559742/article/category/6451395
http://blog.csdn.net/mlc1218559742/article/details/52776160

猜你喜欢

转载自blog.csdn.net/jtracydy/article/details/76061026
今日推荐