深入理解Java中的反射

目录

 

什么是反射:

反射的API:

反射的应用:

反射的缺点:


什么是反射:

      先来一个定义:对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为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.会造成一些性能问题

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值