一篇文章弄懂Java反射基础和反射的应用场景
文章目錄
- 一、Java反射定義
 - 二、Java反射機制實現
 - 1、Class對象獲取
 - 2、獲取class對象的摘要信息
 - 3、獲取class對象的屬性、方法、構造函數等
 
- 三、反射的應用場景
 - 1、動態代理
 - 2、自定義注解實現日志管理
 
寫在前面:Java反射是我們做項目中必備的技能,本篇文章將重新學習反射的基本用法、反射的應用場景等。
一、Java反射定義
JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取的以及動態調用對象的方法的功能稱為Java的反射機制
二、Java反射機制實現
1、Class對象獲取
在一個JVM中,一種類,只會有一個類對象存在。
public class Person {private String name;private int age;private String address; } // 省略get、set方法 private void setCardId(String id) {System.out.println(id);}獲取類對象有3種方式
- Class.forName()
 - Person.class
 - new Person().getClass()
 
第三種方式這里也通過捕獲異常,因為我們傳的這個字符串可能不合法,字符串合法命名是類的命名空間和類的名稱組成。
2、獲取class對象的摘要信息
以下例子將判斷Class對象是否是基礎類型、是否是集合類、是否是注解類、是否是接口類、是否是枚舉類、是否是匿名內部類、是否被某個注解類修飾、獲取class的包信息、獲取class類名、獲取class訪問權限、內部類、外部類。
獲取Person的Class對象
Class class1 = Person.class;判斷是否是基礎類型
boolean isPrimitive = class1.isPrimitive();判斷是否是集合類
boolean isArray = class1.isArray();判斷是否是注解類
boolean isAnnotation = class1.isAnnotation();判斷是否是接口類
boolean isInterface = class1.isInterface();判斷是否是枚舉類
boolean isEnum = class1.isEnum();判斷是否是匿名內部類
boolean isAnonymousClass = class1.isAnonymousClass();判斷是否被某個注解類修飾
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);獲取class名字 包含包名路徑
String className = class1.getName();獲取class的包信息
Package aPackage = class1.getPackage();獲取class類名
String simpleName = class1.getSimpleName();獲取class訪問權限
int modifiers = class1.getModifiers();內部類
Class<?>[] declaredClasses = class1.getDeclaredClasses();外部類
Class<?> declaringClass = class1.getDeclaringClass();3、獲取class對象的屬性、方法、構造函數等
- 獲取class對象的屬性
 
getFields和getDeclaredFields的區別:
 getFields 只能獲取public的,包括從父類繼承來的字段。
 getDeclaredField 可以獲取本類所有的字段,包括private的,但是不能獲取繼承來的字段。 (注: 這里只能獲取到private的字段,但并不能訪問該private字段的值,除非加上setAccessible(true))。
Field常用方法field.set()舉例:
 field.set()方法獲取屬性并修改。
- 獲取class對象的方法
 
獲取class對象的所有聲明方法
Method[] methods = class1.getDeclaredMethods();獲取class對象的所有方法 包括父類的方法
Method[] allMethods = class1.getMethods();如何調用類的私有方法
先在測試類中編寫一個測試的私有方法
private void setCardId(String id) {System.out.println(id);}正常的調用類的方法都是通過類.方法調用,所以我們調用私有方法也需要得到類的實例。
 首先通過 getDeclaredMethod方法獲取到這個私有方法,第一個參數是方法名,第二個參數是參數類型。
 然后通過invoke方法執行,invoke需要兩個參數一個是類的實例,一個是方法參數。
- 獲取class對象的父類
 
獲取class對象的父類
Class parentClass = class1.getSuperclass();獲取class對象的所有接口
Class<?>[] interfaceClasses = class1.getInterfaces();- 獲取class對象構造函數
 
獲取class對象的所有聲明構造函數,但是不能獲取父類的構造函數
Constructor<?>[] allConstructors = class1.getDeclaredConstructors();獲取class對象public構造函數,但是不能獲取私有的構造函數
Constructor[] constructors = class1.getConstructors();獲取類中特定的構造方法并創建對象
step1:可以通過getDeclaredConstructor()方法傳參獲取特定參數類型的構造方法,這里要進行異常捕獲,因為可能不存在對應的構造方法。
Class[] clz = {String.class,int.class}; Constructor declaredConstructor = class1.getDeclaredConstructor(clz);step2:調用newInstance()方法創建對象
Person person1 = (Person) declaredConstructor.newInstance("趙云", 25);- 獲取類的泛型類型
在平常寫代碼時會遇到這樣的寫法 parameterizedType.getActualTypeArguments()[0],這樣寫是什么意思呢。 
getClass().getGenericSuperclass()返回表示此 Class 所表示的實體(類、接口、基本類型或 void)的直接超類的 Type,然后將其轉ParameterizedType。
 getActualTypeArguments()返回表示此類型實際類型參數的 Type 對象的數組。
 [0]就是這個數組中第一個了,簡而言之就是獲得超類的泛型參數的實際類型。
這篇文章講的很詳細:Java反射獲取實際泛型類型參數
三、反射的應用場景
1、動態代理
利用反射機制在運行時創建代理類(method.invoke)。
 接口、被代理類不變,我們構建一個handler類來實現InvocationHandler接口。
下面我們看具體事例。創建接口、實現類、代理類
//接口 public interface Anmail {void name(); } //實現類 public class Cat implements Anmail {@Overridepublic void name() {System.out.println("小貓咪");} }//代理類 public class ProxyHandler implements InvocationHandler {private Object target;public ProxyHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object object, Method method, Object[] args) throws Throwable {System.out.println("Before invoke " + method.getName());Object invoke = method.invoke(target, args);System.out.println("After invoke " + method.getName());return invoke;}public static void main(String[] args) {Cat cat = new Cat();ProxyHandler proxyHandler = new ProxyHandler(cat);Anmail proxy = (Anmail)(Proxy.newProxyInstance(cat.getClass().getClassLoader(), cat.getClass().getInterfaces(), proxyHandler));proxy.name();} }方法newProxyInstance就會動態產生代理類,并且返回給我們一個實例,實現了Anmail 接口。這個方法需要三個參數,第一個ClassLoader并不重要;第二個是接口列表,即這個代理類需要實現那些接口,因為JDK的Proxy是完全基于接口的,它封裝的是接口的方法而不是實體類;第三個參數就是InvocationHandler的實例,它會被放置在最終的代理類中,作為方法攔截和代理的橋梁。注意到這里的handler包含了一個Person實例。
總結一下JDK Proxy的原理,首先它是完全面向接口的,其實這才是符合代理模式的標準定義的。我們有兩個類,被代理類Person和需要動態生成的代理類ProxyClass,都實現了接口Anmail。類ProxyClass需要攔截接口Anmail上所有方法的調用,并且最終轉發到實體類Person上,這兩者之間的橋梁就是方法攔截器InvocatioHandler的invoke方法。
2、自定義注解實現日志管理
AOP概念在這里不在介紹詳情請看這兩篇文章:aop概念和7個專業術語
 AOP基本概念和使用
切入點語法有兩種:
- @Pointcut(“包名…”)
 
- @Pointcut("@annotation(自定義注解)")
 
下面將以自定義注解舉例說明
有兩個類需要重點關注
- Joinpoint
 
- ProceedingJoinPoint
ProceedingJoinPoint繼承JoinPoint子接口,它新增了兩個用于執行連接點方法的方法 
自定義注解
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log {String value() default ""; //用于描述方法作用/*** 是否忽略返回值,僅方法上有效* @return*/boolean ignoreReturn() default false;}切面類LogAspect
@Aspect @Component public class LogAspect {private static final Logger log = LoggerFactory.getLogger(LogAspect.class);private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";private static final String STRING_START = "\n--------------------------->\n";private static final String STRING_END = "\n<----------------------------\n";@Pointcut("@annotation(Log)")public void logPointCut() {}public Object controllerAround(ProceedingJoinPoint joinPoint) {try {return printLog(joinPoint);} catch (Throwable throwable) {log.error(throwable.getMessage(), throwable);return true;}}//通知:攔截到連接點之后要執行的代碼@Around("logPointCut()")private Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {//獲取連接點的方法簽名MethodSignature signature = (MethodSignature) joinPoint.getSignature();//獲取方法簽名里的方法:方法簽名里有兩個方法:getReturnType getMethodMethod method = signature.getMethod();//獲取類Class<?> targetClass = method.getDeclaringClass();StringBuffer classAndMethod = new StringBuffer();// 獲取目標方法上的Log注解Log methodAnnotation = method.getAnnotation(Log.class);// 判斷是否有LOG注解以及是否帶有ignore參數if (methodAnnotation != null) {classAndMethod.append(methodAnnotation.value());}//拼接目標切入的類名稱和方法名稱String target = targetClass.getName() + "#" + method.getName();// 請求參數轉JSON,對日期進行格式轉換并打印出所有為null的參數String params = JSONObject.toJSONStringWithDateFormat(joinPoint.getArgs(), dateFormat, SerializerFeature.WriteMapNullValue);//日志打印拼接的調用信息log.info(STRING_START + "{} 開始調用--> {} 參數:{}", classAndMethod.toString(), target, params);long start = System.currentTimeMillis();//proceed()通過反射執行目標對象的連接點處的方法;Object result = joinPoint.proceed();long timeConsuming = System.currentTimeMillis() - start;if (methodAnnotation != null && methodAnnotation.ignoreReturn()) {log.info("\n{} 調用結束<-- {} 耗時:{}ms" + STRING_END, classAndMethod.toString(), target, timeConsuming);return result;}// 響應參數轉JSON,對日期進行格式轉換并打印出所有為null的參數log.info("\n{} 調用結束<-- {} 返回值:{} 耗時:{}ms" + STRING_END, classAndMethod.toString(), target, JSONObject.toJSONStringWithDateFormat(result, dateFormat, SerializerFeature.WriteMapNullValue), timeConsuming);return result;}}運行結果:
--------------------------->開始調用--> com.sl.dao.UserDao#save 參數:[{"age":"20","id":null,"name":"張三"}] 2020-04-06 14:45:05.695 INFO 16864 --- [nio-8080-exec-4] com.sl.aspect.LogAspect : 調用結束<-- com.sl.dao.UserDao#save 返回值:null 耗時:8ms <----------------------------上面代碼所有源碼地址為:反射基礎部分和自定義AOP源碼
總結
以上是生活随笔為你收集整理的一篇文章弄懂Java反射基础和反射的应用场景的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: HashMap之三问为什么及性能问题
 - 下一篇: 一篇文章弄懂Java多线程基础和Java