日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

一篇文章弄懂Java反射基础和反射的应用场景

發布時間:2025/1/21 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一篇文章弄懂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()
//第一種方式 通過對象getClass方法 Person person = new Person(); Class<?> class1 = person.getClass(); //第二種方式 通過類的class屬性 class1 = Person.class; try {//第三種方式 通過Class類的靜態方法——forName()來實現class1 = Class.forName("com.sl.Person"); } catch (ClassNotFoundException e) {e.printStackTrace(); }

第三種方式這里也通過捕獲異常,因為我們傳的這個字符串可能不合法,字符串合法命名是類的命名空間和類的名稱組成。

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對象的屬性
Field[] fields = class1.getFields(); Field[] declaredFields = class1.getDeclaredFields();

getFields和getDeclaredFields的區別:
getFields 只能獲取public的,包括從父類繼承來的字段。
getDeclaredField 可以獲取本類所有的字段,包括private的,但是不能獲取繼承來的字段。 (注: 這里只能獲取到private的字段,但并不能訪問該private字段的值,除非加上setAccessible(true))。

Field常用方法field.set()舉例:
field.set()方法獲取屬性并修改。

Person person = new Person();person.setName("張三");Field field = person.getClass().getDeclaredField("name");field.setAccessible(true);field.set(person,"李四");System.out.println(person.getName());
  • 獲取class對象的方法

獲取class對象的所有聲明方法

Method[] methods = class1.getDeclaredMethods();

獲取class對象的所有方法 包括父類的方法

Method[] allMethods = class1.getMethods();

如何調用類的私有方法

先在測試類中編寫一個測試的私有方法

private void setCardId(String id) {System.out.println(id);}

正常的調用類的方法都是通過類.方法調用,所以我們調用私有方法也需要得到類的實例。
首先通過 getDeclaredMethod方法獲取到這個私有方法,第一個參數是方法名,第二個參數是參數類型。
然后通過invoke方法執行,invoke需要兩個參數一個是類的實例,一個是方法參數。

Class[] str = {String.class};Method method= class1.getDeclaredMethod("setCardId", str);method.setAccessible(true);setName.invoke(person,"123333333");
  • 獲取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]就是這個數組中第一個了,簡而言之就是獲得超類的泛型參數的實際類型。

Type type = class1.getGenericSuperclass();Type[] genericInterfaces = class1.getGenericInterfaces();Class<?> componentType = null;String typeName = type.getTypeName();if (type instanceof ParameterizedType) {Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();if (actualTypeArguments != null && actualTypeArguments.length > 0) {componentType = (Class<?>) actualTypeArguments[0];}} else if (type instanceof GenericArrayType) {// 表示一種元素類型是參數化類型或者類型變量的數組類型componentType = (Class<?>) ((GenericArrayType) type).getGenericComponentType();}else {componentType = (Class<?>) type;}

這篇文章講的很詳細: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(“包名…”)
//任何類的任何返回值的任何方法 execution(public * *(..)) //任何類的set開頭的方法 execution(* set*(..)) //任何返回值的規定類里面的方法 execution(* com.example.demo.AccountService.*(..))//任何返回值的,規定包或者規定包子包的任何類任何方法 execution(* com.example.demo.service..*.*(..))
  • @Pointcut("@annotation(自定義注解)")

下面將以自定義注解舉例說明

有兩個類需要重點關注

  • Joinpoint
Signature getSignature() /*獲取連接點的方法簽名對象*/
  • ProceedingJoinPoint
    ProceedingJoinPoint繼承JoinPoint子接口,它新增了兩個用于執行連接點方法的方法
//通過反射執行目標對象的連接點處的方法 - java.lang.Object proceed() throws Throwable //通過反射執行目標對象連接點處的方法,不過使用新的入參替換原來的入參。 - java.lang.Object proceed(java.lang.Object[] args) throws Throwable

自定義注解

@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反射基础和反射的应用场景的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。