一:什么是反射
Java反射是Java被视为动态(或准动态)语言的一个关键性质。JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
二:反射的功能
Java反射机制提供如下功能:
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判段任意一个类所具有的成员变量和方法
在运行时调用任一个对象的方法
在运行时创建新类对象在使用Java的反射功能时,基本首先都要获取类的Class对象,再通过Class对象获取其他的对象
1)获取Class对象的三种方式
//一:通过全限定名获取类对象 Class userClass = Class.forName("com.reflect.domain.User"); System.out.println(userClass); //二:通过类直接获取类对象 Class userClass1 = User.class; System.out.println(userClass1); //三:通过类实例获取对象 User user = new User(); Class userClass2 = user.getClass(); System.out.println(userClass2); System.out.println(userClass.equals(userClass1)); System.out.println(userClass1.equals(userClass2));
运行的结果如下:
注意:在 JVM 中一个类只会有一个 Class 实例,即我们对上面获取的 userClass1,userClass2 ,userClass 进行 equals 比较结果都为true2) Class对象api
Class api: https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
常用的api有一下几个:
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
2.1 ) getDeclaredFields() 和 getField()方法的区别
/** * getDeclaredFields() 和 getField()方法的区别 */ Field [] fields = userClass.getFields(); Field [] declaredFields = userClass.getDeclaredFields(); for(Field item : fields) { System.out.println("fields ---> " + item.getName()); } for(Field item : declaredFields) { System.out.println("Declared --- >" + item.getName()); }
运行结果如下:可见getFields()方法只能获取到公共的属性;
2.2)newInstance的使用
newInstance()如上面描述的通过无参构造来获取实例化对象,如果想调用有参构造来实例化对象可按照如下的做法:
/** * 通过有构造器实例化对象 */ Constructor constructor = userClass.getConstructor(String.class,String.class); User userParam = (User) constructor.newInstance("Roman","123"); System.out.println("----userName--" + userParam.getUserName()); System.out.println("----password--" + userParam.getPassword());
结果如下:
3)拿到类对象后我们即可获取到对应的方法,以及调用方法
/** * 无参调用 */ Method printName = userClass.getMethod("printName",new Class []{}); printName.invoke(constructor.newInstance("Roman","123"),new Object[]{}); /** * 有参调用 */ Method printHello = userClass.getMethod("printHello",String.class); printHello.invoke(userClass.newInstance(),"Roman");
如上代码,getMethod方法中有两个参数,第一个参数为方法名,第二个参数为一个长度可变的参数,它的含义是此方法的多个参数按顺序排列的类对象,假设你的方法为xxxGet(Integer x,String y,Double z),那么你的第二个参数的传参形式为:Integer.class,String.class,Double.calss或者直接使用一个数组new Object[]{Integer.class,String.class,Double.calss},
然后真正的调用为invoke()方法,此方法也有两个参数,第一个参数为方法所在的类的实例,第二个为长度可变的参数,他和上面的参数类型相互呼应,以xxxGet(Integer x,String y,Double z)为例,第二个参数你可以写成2,"Roman",3或者new Object[]{2,"Roman",3};
getMethod()和getDeclaredMethods()的区别;
/** * getMethod() 和 getDeclaredMethods()的区别 * */ Method [] methods = userClass.getMethods(); for(Method item : methods){ System.out.println("--method-- " + item.getName()); } Method [] methodsDec = userClass.getDeclaredMethods(); for(Method item : methodsDec){ System.out.println("--methodDec-- " + item.getName()); }
结果如下:
getDeclaredMethods()可以获取到父类中申明的方法以及本类所有申明的方法而getMethods不行他只能获取到public修饰的方法;--method-- getNumber --method-- printName --method-- printHello --method-- getPassword --method-- getUserName --method-- setUserName --method-- setPassword --method-- setNumber --method-- wait --method-- wait --method-- wait --method-- equals --method-- toString --method-- hashCode --method-- getClass --method-- notify --method-- notifyAll --methodDec-- getNumber --methodDec-- printName --methodDec-- printHello --methodDec-- getPassword --methodDec-- getUserName --methodDec-- setUserName --methodDec-- setPassword --methodDec-- printPwd --methodDec-- setNumber
三:项目中的使用
项目中使用的背景:
近期项目中遇到这种情况,需要频繁的调用第三方接口,由于调用第三方接口时可能因为网络的问题或者其他的什么问题导致调用失败,那么如果这种情况,我们不能因为一次调用失败而让整个业务逻辑执行失败,这个时候我想到的解决方案是给这些调用添加重试机制;那么问题来了
第一:调用第三方接口分类的有好多,而且他们的参数以及返回结果都是不相同的,那么我们就没办法通过一个类或者一个工具方法同一的提取出来;
第二:如果我们每一处调用的地方都加上重试的代码的话,虽然也是可以的但是整体就显得代码臃肿,重复代码增多,
针对上面的问题我们可以使用到上面说的反射,获取指定类中的指定的方法,由于反射的方法调用传参以及返回值都是比较自由的,那么我们就可以将所有的调用整合到一个方法中;
具体f如下:
/**
* 重试机制
*/
public static <T> Object retryAudit (T t , String methodName,Object [] param,Class ... paramClass){
Method cMethod;
int count = 0 ;
Object r = null;
Boolean notNeedFlag = Boolean.TRUE;
String errorMsg = "";
while ( notNeedFlag && count <= 3) {
LOGGER.info("-重试开始 ---- 方法:" + methodName + " 参数:" + JSON.toJSONString(param));
try {
if(param == null) {
cMethod = t.getClass().getMethod(methodName);
r = cMethod.invoke(t);
}else {
cMethod = t.getClass().getMethod(methodName,paramClass);
r = cMethod.invoke(t, param);
}
notNeedFlag = Boolean.FALSE;
return r;
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
errorMsg = e.getMessage();
e.printStackTrace();
} catch (NoSuchMethodException e) {
errorMsg = e.getMessage();
e.printStackTrace();
} catch (SecurityException e) {
errorMsg = e.getMessage();
e.printStackTrace();
} catch (Exception e) {
if(e instanceof BaseException){
errorMsg = ((BaseException) e).getMsg();
}else
{
errorMsg = e.getMessage();
}
e.printStackTrace();
}finally {
if(count > 3){
throw new BaseException(errorMsg);
}
if(!StringUtil.isBlank(errorMsg)){
LOGGER.info("-重试次数:" + count + ",重试错误,原因为:" + errorMsg);
}
try {
Thread.currentThread().sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
count ++;
}
}
return null;
}
如下图:
方法的调用和第 3)点讲的一样,唯一不同的是 3)将的是临时通过newInstance()方法来创建对象,这里是将对象作为参数传入进来,这么做的原因是,由于我们的代码是在具体的业务逻辑中,而且我们的对象是交给spring容器进行管理的,如果我们使用newInstance()临时创建对象那么对象中的属性对象将无法通过spring自动注入,代码可能或出现空指针异常,如果我们通过参数传递进来的话即可避免这种问题;
调用的示例如下:
EsmContractSignParam signParam = new EsmContractSignParam(); signParam.setAccessToken(accessToken); signParam.setCreateContractUrl(createContractUrl); signParam.setObject(object); Object contractObj = CommonUtils.retryAudit(esmRemoteService,"contracts",new Object[]{signParam},EsmContractSignParam.class); EsmContract contract = null; if(contractObj instanceof EsmContract){ contract = (EsmContract) contractObj; }
不足:可能这个是减少了代码的重复,但是有一个缺点就是,方法名不能随意的更改,如果一旦进行更改反射将会报错,找不到对应的方法;
注意:异常处理时InvocationTargetException 这一类异常通常是invoke()当被调用的方法的内部抛出了异常而没有被捕获时,将由此异常接收;那么在catch中处理的使用无法通过e.getMessage()来获取异常信息而是通过e.getTargetException()来获取目标异常在通过getMessage()方法获取异常信息
如此,我们所有的调用第三方的方法都可以同一使用这个,减少代码的冗余;
文章简单记录下反射的基本使用以及记录项目中情况