Java回顧之反射


  第一篇:Java回顧之I/O

  第二篇:Java回顧之網絡通信

  第三篇:Java回顧之多線程

  第四篇:Java回顧之多線程同步

  第五篇:Java回顧之集合

  第六篇:Java回顧之序列化

 

  在這一篇文章里,我們關注反射及其相關話題。

  反射可以幫助我們查看指定類型中的信息、創建類型的實例,調用類型的方法。我們平時使用框架,例如Spring、EJB、Hibernate等都大量的使用了反射技術。

  反射簡單示例

  下面來演示反射相關的基本操作

  首先是基礎代碼,我們定義一個接口及其實現,作為我們反射操作的目標:

 1 interface HelloWorldService
 2 {
 3     void sayHello(String name);
 4 }
 5 
 6 class MyHelloWorld implements HelloWorldService
 7 {
 8     public String name;
 9     
10     
11     public void sayHello(String name)
12     {
13         System.out.println("Hello " + name + ".");
14     }
15 
16     public void setName(String name) {
17         this.name = name;
18     }
19 
20     public String getName() {
21         return name;
22     }
23 }

  獲取方法及字段信息  

  下面的代碼會輸出給定類型中的方法和字段的聲明信息:

 1 private static void printClassTypeInfo(String type) throws ClassNotFoundException
 2 {
 3     Class classType = Class.forName(type);
 4     Method[] methods = classType.getDeclaredMethods();
 5     System.out.println("Methods info as below:");
 6     for(Method method : methods)
 7     {
 8         System.out.println(method.toGenericString());
 9     }
10     Field[] fields = classType.getFields();
11     System.out.println("Fields info as below:");
12     for (Field field : fields)
13     {
14         System.out.println(field.toGenericString());
15     }
16 }

  在使用反射時,我們一般會使用java.lang.reflect包中的內容。

  然后我們調用下面的代碼:

1 printClassTypeInfo("sample.reflection.MyHelloWorld");

  輸出結果如下:

Methods info as below:
public void sample.reflection.MyHelloWorld.sayHello(java.lang.String)
public java.lang.String sample.reflection.MyHelloWorld.getName()
public void sample.reflection.MyHelloWorld.setName(java.lang.String)
Fields info as below:
public java.lang.String sample.reflection.MyHelloWorld.name

  實例化對象

  我們可以使用class.netInstance的方式來創建一個對象,代碼如下:

1 private static void createInstanceTest() throws ClassNotFoundException, InstantiationException, IllegalAccessException
2 {
3     Class classType = Class.forName("sample.reflection.MyHelloWorld");
4     MyHelloWorld hello = (MyHelloWorld)classType.newInstance(); 5     hello.sayHello("Zhang San");
6 }

  輸出結果:

Hello Zhang San.

  調用對象的方法

  我們可以通過方法的名稱以及參數類型構建一個Method實例,然后調用Method的invoke方法,來觸發方法。

  示例代碼如下:

1 private static void invokeMethodTest() throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException
2 {
3     Class classType = Class.forName("sample.reflection.MyHelloWorld");
4     MyHelloWorld hello = (MyHelloWorld)classType.newInstance();
5     Method method = classType.getMethod("sayHello", new Class[]{String.class}); 6     method.invoke(hello, new Object[]{"Zhang San"}); 7 }

  輸出結果同上。

  修改字段的值

  和C#不同,Java中一般使用setxxx和getxxx顯示為屬性賦值,因此Java中並沒有Property類型,而是有Field類型。

  我們可以對Field的值進行修改,代碼如下:

1 private static void setFieldTest() throws ClassNotFoundException, NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException
2 {
3     Class classType = Class.forName("sample.reflection.MyHelloWorld");
4     MyHelloWorld hello = (MyHelloWorld)classType.newInstance();
5     System.out.println("name is " + hello.name);
6     Field field = classType.getField("name");
7     field.set(hello, "Zhang San");
8     System.out.println("name is " + hello.name);
9 }

  執行結果如下:

name is null
name is Zhang San

  可以看出,我們成功的修改了name的值。

  Annotation探索

  一開始我們提到,反射是很多技術的基礎,Annotation就是這樣的,我們可以把Annotation看做是C#中的Attribute,它可以對類型、方法、屬性、字段、方法參數等信息進行修飾。我們可以使用“@+Annotation名”的方式來使用Annotation。

  Annotation基本操作

  來看下面的代碼,我們定義了基於Type、Method、Parameter和Field上面的Annotation示例:

 1 @Target(ElementType.TYPE)
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Documented
 4 @interface ClassAnnotation
 5 {
 6     public String value();
 7 }
 8 
 9 @Target(ElementType.METHOD)
10 @Retention(RetentionPolicy.RUNTIME)
11 @Documented
12 @interface MethodAnnotation
13 {
14     public String methodName();
15     public String returnType();
16 }
17 
18 @Target(ElementType.PARAMETER)
19 @Retention(RetentionPolicy.RUNTIME)
20 @Documented
21 @interface ParameterAnnotation
22 {
23     public String value();
24 }
25 
26 @Target(ElementType.FIELD)
27 @Retention(RetentionPolicy.RUNTIME)
28 @Documented
29 @interface FieldAnnotation
30 {
31     public String value();
32 }

  接着,我們定義了一個MyClass類型,使用了上述的Annotation:

 1 @ClassAnnotation("這是作用在類型上的Annotation")
 2 class MyClass
 3 {
 4     @MethodAnnotation(methodName="printInfo", returnType="void")
 5     public void printInfo(String info)
 6     {
 7         System.out.println(info);
 8     }
 9     
10     @MethodAnnotation(methodName="printError", returnType="void")
11     public void printError(@ParameterAnnotation("這是作用在參數上的Annotation")String error)
12     {
13         System.err.println(error);
14     }
15     
16     @FieldAnnotation("這是作用在字段上的Annotation")
17     public int count;
18 }

  對於使用了Annotation,我們可以獲取其中的信息,下面兩種方式都可以獲取Annotation,第一種方式是通過反射遍歷類型及其方法、字段,一一讀取Annotation信息;第二種方式是讀取指定類型的Annotation:

讀取Annotation方式一
 1 private static void annotationTest1()
 2 {
 3     MyClass temp = new MyClass();
 4     
 5     Annotation[] annotations = temp.getClass().getAnnotations();
 6     for(Annotation a : annotations)
 7     {
 8         System.out.println(a.toString());
 9     }
10     
11     Method[] methods = temp.getClass().getDeclaredMethods();
12     for(Method method : methods)
13     {
14         annotations = method.getAnnotations();
15         for(Annotation a : annotations)
16         {
17             System.out.println(a.toString());
18         }
19         Annotation[][] paraAnnotations = method.getParameterAnnotations();
20         for(int i = 0; i < paraAnnotations.length; i++)
21         {
22             for (Annotation a : paraAnnotations[i])
23             {
24                 System.out.println(a.toString());
25             }
26         }
27     }
28     
29     Field[] fields = temp.getClass().getFields();
30     for (Field field : fields)
31     {
32         annotations = field.getAnnotations();
33         for(Annotation a : annotations)
34         {
35             System.out.println(a.toString());
36         }
37     }
38 }
讀取Annotation方式二
 1 private static void annotationTest2() throws ClassNotFoundException
 2 {
 3     Class classType = Class.forName("sample.reflection.annotation.MyClass");
 4     boolean flag = classType.isAnnotationPresent(ClassAnnotation.class);
 5     if (flag)
 6     {
 7         ClassAnnotation annotation = (ClassAnnotation) classType.getAnnotation(ClassAnnotation.class);
 8         System.out.println(annotation.toString());
 9     }
10     Method[] methods = classType.getMethods();
11     for(Method method : methods)
12     {
13         if (method.isAnnotationPresent(MethodAnnotation.class))
14         {
15             System.out.println(((MethodAnnotation)method.getAnnotation(MethodAnnotation.class)).toString());
16         }
17         Annotation[][] paraAnnotations = method.getParameterAnnotations();
18         for(int i = 0; i < paraAnnotations.length; i++)
19         {
20             for (Annotation a : paraAnnotations[i])
21             {
22                 System.out.println(a.toString());
23             }
24         }
25     }
26     Field[] fields = classType.getFields();
27     for (Field field:fields)
28     {
29         if (field.isAnnotationPresent(FieldAnnotation.class))
30         {
31             System.out.println(((FieldAnnotation)field.getAnnotation(FieldAnnotation.class)).toString());
32         }
33     }
34 }

  上述兩個方法的輸出都是一樣的,如下:

@sample.reflection.annotation.ClassAnnotation(value=這是作用在類型上的Annotation)
@sample.reflection.annotation.MethodAnnotation(methodName=printInfo, returnType=void)
@sample.reflection.annotation.MethodAnnotation(methodName=printError, returnType=void)
@sample.reflection.annotation.ParameterAnnotation(value=這是作用在參數上的Annotation)
@sample.reflection.annotation.FieldAnnotation(value=這是作用在字段上的Annotation)

  在WebService中使用Annotation

  上述代碼看上去可能有些枯燥,不能顯示出Annotation的威力,那么我們接下來看WebService,在WebService中,我們可以使用WebMethod、WebParam等Annotation來聲明方法或者參數。

  接下來,我們來實現一個非常簡單的Web服務:

 1 @WebService(targetNamespace="http://test", serviceName="HelloService")
 2 public class HelloServiceProvider
 3 {
 4     @WebResult(name="HelloString")
 5     @WebMethod
 6     public String sayHello(@WebParam(name="userName") String name)
 7     {
 8         return "Hello " + name;
 9     }
10     
11     @Oneway
12     @WebMethod(action="userLogin", operationName="userLogin")
13     public void login()
14     {
15         System.out.println("User has logged on.");
16     }
17     
18     public static void main(String[] args)
19     {
20         Thread thread = new Thread(new HelloServicePublisher());
21         thread.start();
22     }
23 }

  然后定義一個Publisher:

1 class HelloServicePublisher implements Runnable
2 {
3     public void run()
4     {
5         Endpoint.publish("http://localhost:8888/test/HelloService", new HelloServiceProvider());
6     }
7 }

  在命令行中,我們定位到源代碼路徑,執行下面的命令:

wsgen -cp . HelloServiceProvider

  wsgen位於JDK的bin目錄中。

  然后我們啟動HelloServiceProvider,在瀏覽器中輸入如下地址:http://localhost:8888/test/HelloService,可以看到如下信息:

  點擊WSDL鏈接,可以看到:

WSDL信息
<!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.4-b01. --><!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.4-b01. --><definitions targetNamespace="http://test" name="HelloService"><types><xsd:schema><xsd:import namespace="http://test" schemaLocation="http://localhost:8888/test/HelloService?xsd=1"/></xsd:schema></types><message name="sayHello"><part name="parameters" element="tns:sayHello"/></message><message name="sayHelloResponse"><part name="parameters" element="tns:sayHelloResponse"/></message><message name="userLogin"><part name="parameters" element="tns:userLogin"/></message><portType name="HelloServiceProvider"><operation name="sayHello"><input wsam:Action="http://test/HelloServiceProvider/sayHelloRequest" message="tns:sayHello"/><output wsam:Action="http://test/HelloServiceProvider/sayHelloResponse" message="tns:sayHelloResponse"/></operation><operation name="userLogin"><input wsam:Action="userLogin" message="tns:userLogin"/></operation></portType><binding name="HelloServiceProviderPortBinding" type="tns:HelloServiceProvider"><soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/><operation name="sayHello"><soap:operation soapAction=""/><input><soap:body use="literal"/></input><output><soap:body use="literal"/></output></operation><operation name="userLogin"><soap:operation soapAction="userLogin"/><input><soap:body use="literal"/></input></operation></binding><service name="HelloService"><port name="HelloServiceProviderPort" binding="tns:HelloServiceProviderPortBinding"><soap:address location="http://localhost:8888/test/HelloService"/></port></service></definitions>

  JDK中自帶了Web服務器,我們不需要把上述代碼部署到其他服務器中。

  動態代理機制

  Spring中一大特色是AOP,面向方面編程也是框架設計一個趨勢。對於業務中的共通操作,諸如記錄日志、維護事務等,如果和業務邏輯糾纏在一起,會造成代碼職責不清,后續維護困難等問題。利用AOP,我們可以很好的分離共通操作和業務操作。

  下面我們來實現一個簡單的AOP框架,要實現這樣一個框架,需要3部分:1)InvocationHandler,來觸發方法;2)Interceptor,來定義攔截器;3)DynamicProxy,來動態創建代理對象。

  首先我們看Interptor的定義:

1 interface AOPInterceptor
2 {
3     public void before(Method method, Object[] args);
4     public void after(Method method, Object[] args);
5     public void afterThrowing(Method method, Object[] args);
6     public void afterFinally(Method method, Object[] args);
7 }

  接下來是InvocationHandler:

 1 class DynamicProxyInvocationHandler implements InvocationHandler
 2 {
 3     private Object target;
 4     private AOPInterceptor interceptor;
 5 
 6     public DynamicProxyInvocationHandler(Object target, AOPInterceptor interceptor)
 7     {
 8         this.target = target;
 9         this.interceptor = interceptor;
10     }
11     
12     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
13     {
14         try
15         {
16             interceptor.before(method, args);
17             Object returnValue = method.invoke(target, args);
18             interceptor.after(method, args);
19             return returnValue;
20         }
21         catch(Throwable t)
22         {
23             interceptor.afterThrowing(method, args);
24             throw t;
25         }
26         finally
27         {
28             interceptor.afterFinally(method, args);
29         }
30     }
31 }

  最后是DynamicProxy:

1 class DynamicProxyFactoryImpl implements DynamicProxyFactory
2 {
3     public <T> T createProxy(Class<T> clazz, T target, AOPInterceptor interceptor)
4     {
5         InvocationHandler handler = new DynamicProxyInvocationHandler(target, interceptor);
6         return (T)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[] {clazz}, handler);
7     }
8 }

  至此,我們構建了一個”簡易“的AOP攔截器。下面我們來創建一些測試代碼。

  首先是實現AOPInterceptor接口:

 1 class MyInterceptor implements AOPInterceptor
 2 {
 3 
 4     public void after(Method method, Object[] args) {
 5         System.out.println("方法執行結束。");
 6     }
 7 
 8     public void afterFinally(Method method, Object[] args) {
 9         System.out.println("方法體Finally執行結束。");
10     }
11 
12     public void afterThrowing(Method method, Object[] args) {
13         System.out.println("方法拋出異常。");
14     }
15 
16     public void before(Method method, Object[] args) {
17         System.out.println("方法開始執行");
18     }
19 }

  然后利用本文一開始定義的HelloWorldService,來完成測試,需要在MyHello的sayHello方法最后,追加一行代碼:

1 throw new RuntimeException();

  接着是測試代碼:

1 private static void test()
2 {
3     MyInterceptor interceptor = new MyInterceptor();
4     HelloWorldService hello = new MyHelloWorld();
5     DynamicProxyFactory factory = new DynamicProxyFactoryImpl();
6     HelloWorldService proxy = factory.createProxy(HelloWorldService.class, hello, interceptor);
7     proxy.sayHello("Zhang San");
8 }

  最終,執行結果如下:

方法開始執行
Hello Zhang San.
方法拋出異常。
方法體Finally執行結束。
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
    at sample.reflection.dynamicproxy.$Proxy0.sayHello(Unknown Source)
    at sample.reflection.dynamicproxy.Sample.test(Sample.java:18)
    at sample.reflection.dynamicproxy.Sample.main(Sample.java:9)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sample.reflection.dynamicproxy.DynamicProxyInvocationHandler.invoke(Sample.java:60)
    ... 3 more

  可以看出,我們已經在業務執行的前、后、異常拋出后以及finally執行后進行了攔截,達到了我們期望的效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM