什么是Java反射机制?
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
java反射机制在java中主要做什么工作?
Java的中的引用有两个类型,一种是编译时类型,一种是运行时类型。
例如,
有Person,Man extends Person 两个类;两者是父子关系
Person man = new Man();
在编译时,man引用的类型是Person, 但是在程序运行时,man的引用类型实则是Man; 这就是多态。
多态的详细解释:https://www.runoob.com/java/java-polymorphism.html
反射的工作(运行时):在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。
在实际运用中java的反射机制
对于一个字节码文件.class,虽然表面上我们对该字节码文件一无所知,但该文件本身却记录了许多信息。Java在将.class字节码文件载入时,JVM将产生一个java.lang.Class对象代表该.class字节码文件,从该Class对象中可以获得类的许多基本信息,这就是反射机制。所以要想完成反射操作,就必须首先认识Class类。
例如:Class personClass = Person.class;
Class类:
Class类是一个比较特殊的类,它是反射机制的基础,Class类的对象表示正在运行的Java程序中的类或接口,也就是任何一个类被加载时,即将类的.class文件(字节码文件)读入内存的同时,都自动为之创建一个java.lang.Class对象。Class类没有公共构造方法,其对象是JVM在加载类时通过调用类加载器中的defineClass()方法创建的 ;
defineClass()这个类是classloader里的,classloader是将class字节类载入虚拟机的一种形式,为了给外界提供一种加载class的途径。
java反射机制为java带来了什么?
首先,反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
其次,通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
再次,使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
最后,反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。
正是反射有以上的特征,所以它能动态编译和创建对象,极大的激发了编程语言的灵活性,强化了多态的特性,进一步提升了面向对象编程的抽象能力,因而受到编程界的青睐。 [3]
java反射机制的缺点
尽管反射机制带来了极大的灵活性及方便性,但反射也有缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射完成时,尽量不要使用,原因有以下几点:
1、性能问题。
Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。
2、安全限制。
使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。
3、程序健壮性。
反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。 [2]
为什么ioc用了java反射机制原理呢?怎么用的?
ioc容器后,我们无需用关键字new对象。
只需要在配置文件中获取到该.java文件的完全限定名,通过.class、getClass(),class.forName();
就可以获取到它的.class字节码文件, 通过获取字节码文件的信息,创建一个对象代表这个字节码文件。
主要: 字节码文件存储的是类元信息。 一切方法和变量都在。
这样这个对象,就拥有了这个字节码文件的所有功能。其实这个东东本质上就是原来的类对象。不过我们不new可了,而是通过反射帮我们创造出来。
Spring(IOC)
Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
IOC—Inversion of Control,是一种设计思想 。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
我用了ioc容器后,对其大致步骤的理解是,通过接口实现多态,利用反射机制在程序运行时获得对象。
以下我会先介绍反射,再用反射实现ioc容器的基本代码。
java反射的基本用法:
注意: 反射创建对象,是根据类中的构造方法创建的,如果没有这个构造方法,则会报错。也就是说,想要利用反射创建类对象,这个类必须是已知的,已经建立过的,已经拥有对应的字节码文件的。
以下是一个Person01类, 分别包括:空构造、有参构造、public + 参、private、protected、 return String返回值。 这几个典型方法。
package com.ssm.reflect;
public class Person01 {
private String name;
private int age;
public String sex;
public Person01(){}
public Person01(String name,int age){
this.name = name;
this.age = age;
}
/**
* public
* @param what
*/
public void eat(String what){
System.out.println(name+"吃"+what);
}
/**
* private
*/
private void run(){
System.out.println(name+"在跑");
}
/**protected
*
*/
protected void sleep(){
System.out.println(name+"在睡");
}
/**
* 有返回值的
* @return
*/
@Override
public String toString(){
return "姓名:"+this.name+",年龄:"+this.age;
}
}
- 通过反射获取这个类的Class对象;
Class<Person01> person01Class =Person01.class;
-
来看看这个对象是不是拥有了Person01类的所有信息;
一、 获取类的空构造方法与有参构造方法
Constructor constructor = person01Class.getConstructor();
Constructor constructor01 = person01Class.getConstructor(String.class,int.class);
/*获取两个构造方法的名称和其对应的所有参数, 01是有参数的*/
System.out.println("无参构造方法名称:"+constructor.getName());
System.out.println("有参构造方法名称:"+constructor01.getName());
结果: 无参构造方法名称:com.ssm.reflect.Person01
有参构造方法名称:com.ssm.reflect.Person01
/*打印有参构造方法的所有参数名*/
Class[] x =constructor01.getParameterTypes();
for(int i=0;i<x.length;i++){
System.out.print("有参构造方法的参数名"+i+":"+x[i].getName());
System.out.print("\t");
}
结果: 有参构造方法的参数名0:java.lang.String 有参构造方法的参数名1:int
会发现,
空构造与有参构造name相同。
不论是String类还是int类还是Person01类,名称是该类的完全限定名。
二、获取该类的成员变量
Field[] fields = person01Class.getDeclaredFields();
Field[] fields1 = person01Class.getFields();
//源码解释:
/*
getDeclaredFields(): 包括public, protected, default (包)访问和私有字段,但不包括继承字段。如果这个{@code Class}对象表示一个没有声明的字段,然后此方法返回长度为0的数组。
getFields(): 其中包含{@code Field}对象,反映所有
类或接口的可访问公共字段,这个{@code Class}对象。如果这个{@code Class}对象表示一个没有
没有可访问的公共字段,那么这个方法返回一个长度数组
*/
//输出方式
for(Field f : fields){
// 获取变量修饰符
int mod = f.getModifiers();
System.out.println("变量修饰符:"+ Modifier.toString(mod));
// 获取成员变量的类型
Class type = f.getType();
System.out.println("变量类型:"+type.getName());
// 获取成员变量的名称
System.out.println("变量名称:"+f.getName());
}
可以发现,两个方法都是获取成员变量的,第一个可以获取到所有成员变量(但不包括继承字段), 第二个只能获取到公共变量(但可以获取继承字段);
输出结果如下:
三、获取该类的所有方法
Method[] methods = person01Class.getDeclaredMethods();
Method[] methods1 =person01Class.getMethods();
/*
源码解释:
getDeclaredMethods(): 返回一个包含{@code Method}对象的数组
声明的类或接口的方法由这个{@code
Class}对象,包括public, protected, default (package)
访问和私有方法,但不包括继承方法。
getMethods(): 返回一个包含{@code Method}对象的数组
类或接口的公共方法由这个{@code表示
对象,包括类或接口声明的对象
从超类和超接口继承的。
*/
//输出方式:
for (Method method : methods){
//打印方法名
System.out.println("方法名:"+method.getName());
//获取方法参数
Parameter[] parameters =method.getParameters();
for(Parameter parameter : parameters){
System.out.println("参数名:"+parameter.getName());
System.out.println("参数类型:"+parameter.getType());
}
}
可以发现,两个方法都是获取方法的,第一个可以获取到所有方法(但不包括继承方法), 第二个只能获取到公共变量(但可以获取继承字方法);
输出结果如下:
接下来实现ioc基础设计思想:
- 写接口
public interface DisplayDao {
void print();
}
- 写接口实现类 并将其交给配置文件管理。
@Component
public class DisplayDaoImpl implements DisplayDao {
@Override
public void print() {
System.out.println("测试成功!");
}
}
- 写配置文件 启动注解配置,扫描接口实现类所在的包。
<context:annotation-config></context:annotation-config>
<context:component-scan base-package="com.ssm.reflect"></context:component-scan>
<bean id = "lg" class="com.ssm.reflect.DisplayDaoImpl"></bean>
- 利用类加载器的getResourceAsStream()方法读取xml资源,并将其转为输入流,再借用工具SAXReader 读取xml配置文件的输入流;
第一步: 导入dom4j 的jar包。
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
第二步: 建立处理配置文件的工厂, 建立getBean()方法。
public class FactoryBean {
public static Object getBean(String arg) throws Exception{
SAXReader reader = new SAXReader();
/*利用类加载器的getResourceAsStream()方法读取xml资源,并将其转为输入流,*/
InputStream inputStream = FactoryBean.class.getClassLoader().getResourceAsStream("ApplicationContext.xml");
/*再借用工具SAXReader 读取xml配置文件的输入流;转为文档格式\*/
Document dc = reader.read(inputStream);
/*声明类的完全限定名*/
String classValue = null;
/*获取文档的根元素*/
Element root = dc.getRootElement();
System.out.println("root:"+root);
/*获取根元素中的所有子元素*/
List<Element> elementList = root.elements();
for(Element element : elementList){
System.out.println("element: "+element);
/*如果子元素中有一个元素名称为bean;*/
if(element.getName().equals("bean")){
/*如果这个bean元素的id值为参数arg*/
if(element.attributeValue("id").equals(arg)){
System.out.println("id :"+element.attributeValue("id"));
/*那么就获取它的class属性*/
classValue = element.attributeValue("class");
System.out.println("class: "+classValue);
}
}
}
/**
* classValue = com.ssm.reflect.DisplayDaoImpl
*
* 再利用反射机制生成类。
*/
Class classes = Class.forName(classValue);
System.out.println("classes: "+classes);
Object obj = classes.newInstance();
System.out.println("obj: "+obj);
return obj;
}
}
- 建立测试类
public class TestIoc {
public static void main(String[] args) {
try {
DisplayDao displayDao = (DisplayDaoImpl) FactoryBean.getBean("lg");
displayDao.print();
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果如下:
可以发现,测试成功了!
外话: 在我前一个关于mybatis框架的博文中,提到这些:
在使用mybatis持久层框架时,它是这样读取xml文件,并且打开会话的。
public class SqlSessionFactoryUtil {
//创建会话sqlsession对象
private static SqlSessionFactory sessionFactory;
static {
Reader reader = null;
try {
//利用Resources方法读取xml配置文件,获取配置信息。
reader = Resources.getResourceAsReader("mybatis.xml");
//根据读取到的配置信息新建立一个会话。
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//打开会话,现在就可以操作数据库了。
public static SqlSession getSession(){
return sessionFactory.openSession();
}
}
可以发现,它也是读取了xml配置文件,这是另一种读取方式。
在我的测试类中,我是这样获取代理对象的。
SqlSession sqlSession = SqlSessionFactoryUtil.getSession();
DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
可以发现,这个用的Facotory+xml+反射实现的。这里给getMapper方法传了一个 dao.class。这里先提一下,作个区分。下次会写jdk动态代理是如何实现代理对象的。
下面是两个大佬对ioc的陈述:
介绍IoC/DI基本思想的演变 的:https://www.cnblogs.com/xinhuaxuan/p/6128523.html
(力荐!通俗易懂)讲IOC/DI的:https://www.iteye.com/blog/jinnianshilongnian-1413846