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执行后进行了拦截,达到了我们期望的效果。

你可能感兴趣的:(java)