目录
什么是反射:
先来一个定义:对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制(注意关键词:运行状态)换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods
那么原理是什么呢?
我们来看一段代码 :
A a = new A();这个通过一般的方式来创建一个对象,那么其详细的过程是:
分析一下:我们需要A类,先把A.class字节码加载到内存中,然后通过一系列的操作创建出该类的对象。其实加载完该字节码之后,在堆的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),该对象包含了类的所有信息,我们可以通过class对象来获取类的整体结构(包括有什么方法,有什么属性,有什么注解啊等等),所以我们形象地称之为反射
反射的API:
Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;
其中class代表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象,通过这四个对象我们可以粗略的看到一个类的各个组成部分。
public class Test {
public static void main(String[] args) throws Exception {
// 通过全限定报名来加载类信息
Class class1 = Class.forName("User");
Class class2 = Class.forName("User");
// 结果为true,可见一个类只会有一个class对象
System.out.println(class1 == class2);
// 反射时调用方法
Method method = class1.getMethod("setAge", int.class);
// 注意:newInstance是利用无参构造函数来创建的
// 如果用这个的时候,你自己写了构造函数覆盖了无参方法,会有异常
/*
* Exception in thread "main" java.lang.InstantiationException: User at
* java.lang.Class.newInstance(Class.java:427) at Test.main(Test.java:21) Caused
* by: java.lang.NoSuchMethodException: User.<init>() at
* java.lang.Class.getConstructor0(Class.java:3082) at
* java.lang.Class.newInstance(Class.java:412) ... 1 more
*/
//User user = (User) class1.newInstance();
//如果有有参方法,则需要先获取构造方法
Constructor constructor = class1.getConstructor(int.class, String.class);
User user = (User) constructor.newInstance(12, "何建锋");
method.invoke(user, 233);
System.out.println(user.getAge()+"名字是:"+user.getName());
//获取所有属性
Field []fields = class1.getDeclaredFields();
for(Field field :fields) {
System.out.println(field);
}
//获取所有的方法
Method []methods = class1.getMethods();
for(Method method2 :methods) {
System.out.println(method2);
}
Field name = class1.getDeclaredField("name");
//不能直接操作私有属性,需要关闭安全检测
name.setAccessible(true);
name.set(user, "何建发那个");
System.out.println("获取name属性:"+user.getName());
}
}
反射的应用:
案例一:
Spring框架中的应用
Java的反射特性一般结合注解和配置文件(如:XML)来使用,这也是大部分框架(Spring等)支持两种配置方式的原因。
在Spring中,如果采用的是注解方式,那么流程是这样的:
当服务端启动时,Spring框架会去扫描指定目录下的类,通过反射看类有没有Service注解,如果类上有 Service注解,会提前初始化(new)这个类。初始化好所有类以后,再去查找所有属性,看属性有没有Autowired注解,有的话,会给这个属性注入值(反射赋值)
如果是配置文件的方式,原理也和注解的差不多,是先解析xml文件,拿到XML里的配置信息,再去初始化(new)或给属性反射赋值。所以我们写业务代码的时候才不用一个个的去new实现类,所有参数都赋上值,这部分工作Spring已经利用反射技术给完成了。
案例二:
想把一个对象的所有属性值存到数据库里,通常会用到
insert into XXX (field1,field2) values (v001,v002)
这样的语句每个对象都要写一遍,那岂不是很麻烦,能不能有一种自动的方式生成呢?
我们假定表名是pojo的类名,字段名是pojo的属性名称,那么我们就可以用反射的方式获取相关的信息进行字符串的拼接,只要输入对象就可以得到我们想要的sql
public class Test2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
User user = new User();
System.out.print(getSaveSQL(user));
}
public static String getSaveSQL(Object javaBean) {
StringBuilder sb = new StringBuilder();
// 由于需要增加的内容较多,固采用StringBuilder代替String
Class<?> targetClass = javaBean.getClass();
// 目标类的Class对象
String className = targetClass.getSimpleName();
// 简单类名
sb.append("insert into ");
sb.append(className);
// 类名即对应表名
sb.append(" (");
Field[] fields = targetClass.getDeclaredFields();
// 获得属性域
List<String> fieldList = new ArrayList<>();
// 存储属性名的数组
List<String> methodList = new ArrayList<>();
// 存储方法名的数组
String fieldName = null;
String methodName = null;
for (Field field : fields) {
// 遍历属性域,获得对应属性名和方法名
fieldName = field.getName();
methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
fieldList.add(fieldName);
methodList.add(methodName);
}
int i;
for (i = 0; i < fieldList.size(); i++) {
// 构建sql语句中的字段部分
sb.append(fieldList.get(i));
if (i != fieldList.size() - 1)
sb.append(",");
}
sb.append(") values (");
Object to = null;
Method tm = null;
try {
for (i = 0; i < methodList.size(); i++) {
// 构建sql语句中的值部分
tm = targetClass.getMethod(methodList.get(i));
to = tm.invoke(javaBean);
if (to instanceof String || to instanceof Boolean) {
sb.append("\'" + to + "\'");
} else {
sb.append(to);
}
if (i != methodList.size() - 1)
sb.append(",");
}
sb.append(")");
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
}
案例三
自定义注解的时候,用反射让注解实现功能,
具体的例子可以看我的另外一篇博客:自定义注解,大体的作用是自定义注解判断对象的属性是否为空
案例四:
比如手机APP的用户注册,一般有验证短信这个功能,有时候不能过度依赖一个第三方发短信的平台,假设这家短信平台的服务挂了,会导致我们的APP无法注册,验证短信等。我们之前短信平台用的是云片网的短信平台,并找了阿里短信平台做备用,由于这两个厂商的API完全不一致,写出来的代码也不一样,大部分人写代码会像下面这样写
发送短信(){
if(云片网发短信开关为开启){
调用第三方云片网发短信API发送短信;
}else if(阿里短信平台发短信开关为开启){
调用第三方阿里短信平台发短信API发送短信;
}else if(....){
...........;//如果有新的需求,增加else if逻辑
}else{
}
}
,第一:假设我们打开了阿里短信平台的开关,但忘了去关闭云片网短信平台的开关,会导致不调用阿里短信平台去发送短信,还是会去调用云片网短信平台去发送短信。 第二:假设某天老板说,这两家短信平台费率太高,要使用其它短信平台,这时候开发人员还会来修改这段这段代码,增加新的else if条件,并写下相关的调用短信平台的代码,这样就违背了类设计的六大设计原则:开闭原则(Open-Close Principle),类的设计应该对扩展开放,对修改关闭。这里明显修改了这个方法(类),一旦修改了这段代码(这个类),测试人员就会对这段代码(这个类)里所有的else if条件(整个类)都要进行覆盖测试。那有没有办法一次写好这段代码,以后就算新增加其它短信平台也不用修改这段代码呢?有,我们可以利用反射来完美实现,先定义一个接口,接口里声明了一个方法发送短信()
interface 短信接口(){
void 发送短信();
}
再写实现类来实现该接口
class 云片网短信接口实现 implments 短信接口{
void 发送短信(){
调用第三方云片网发短信API发送短信;
}
}
class 阿里短信接口实现 implments 短信接口{
void 发送短信(){
调用第三方阿里短信平台发短信API发送短信;
}
}
调用的代码直接三行解决
String 实现类名 = 从数据库或缓存里读取到的实现类名。
短信接口 接口 = Class.forName(实现类名).newInstance();//反射创建子类实例
接口.发送短信();
并且如果有做修改的话,该数据库中的值就可以了。
反射的优缺点:
优点:(在上文列举的案例中均有体现)
1.增加程序的灵活性,避免将程序写死到代码里
2.代码简洁,提高代码的复用率,外部调用方便
3.对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法
缺点:
1.会造成一些性能问题