Use analysis of reflection based on Java retrospectives

  • 2020-04-01 01:51:21
  • OfStack

Reflection can help us view the information in the specified type, create an instance of the type, and invoke the method of the type. We usually use frameworks, such as Spring, EJB, Hibernate, etc., to make extensive use of reflection technology.
Simple example of reflection
Let's demonstrate the basics of reflection

The first is the basic code. We define an interface and its implementation as the target of our reflection operation:


interface HelloWorldService
 {
     void sayHello(String name);
 }

 class MyHelloWorld implements HelloWorldService
 {
     public String name;

     
     public void sayHello(String name)
     {
         System.out.println("Hello " + name + ".");
     }

     public void setName(String name) {
         this.name = name;
     }

     public String getName() {
         return name;
     }
 }

Gets method and field information
The following code prints out the declaration of methods and fields in a given type:

private static void printClassTypeInfo(String type) throws ClassNotFoundException
 {
     Class classType = Class.forName(type);
     Method[] methods = classType.getDeclaredMethods();
     System.out.println("Methods info as below:");
     for(Method method : methods)
     {
         System.out.println(method.toGenericString());
     }
     Field[] fields = classType.getFields();
     System.out.println("Fields info as below:");
     for (Field field : fields)
     {
         System.out.println(field.toGenericString());
     }
 }

When using reflection, we typically use the contents of the java.lang.reflect package.

Then we call the following code:


printClassTypeInfo("sample.reflection.MyHelloWorld");

The output results are as follows:

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

Instantiate object
We can use class.netInstance to create an object as follows:

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

Output results:

Hello Zhang San.

Invoke the method of the object
We can build a Method instance with the name of the Method and the parameter type, and then invoke the Method's invoke Method to trigger the Method.

The sample code is as follows:


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

The output is the same as above.

Modify the value of the field
Unlike C#, setxxx and getxxx are generally used in Java to be displayed for attribute assignment, so there is no Property type in Java, but a Field type.

We can modify the value of the Field, the code is as follows:


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

The implementation results are as follows:

name is null
name is Zhang San

As you can see, we successfully changed the value of the name.

The Annotation to explore
At the beginning, we mentioned that reflection is the basis of many techniques, and Annotation is just like that. We can think of an Annotation as an Attribute in C#, which can modify information such as type, method, Attribute, field, method parameter, etc. We can use annotations the way we would use an "@+Annotation name."

Annotation basic operation
In the following code, we define an Annotation example based on Type, Method, Parameter, and Field:


@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @interface ClassAnnotation
 {
     public String value();
 }

 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @interface MethodAnnotation
 {
     public String methodName();
     public String returnType();
 }

 @Target(ElementType.PARAMETER)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @interface ParameterAnnotation
 {
     public String value();
 }

 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @interface FieldAnnotation
 {
     public String value();
 }

Next, we define a MyClass type that USES the above Annotation:

@ClassAnnotation(" This is on the type Annotation")
 class MyClass
 {
     @MethodAnnotation(methodName="printInfo", returnType="void")
     public void printInfo(String info)
     {
         System.out.println(info);
     }

     @MethodAnnotation(methodName="printError", returnType="void")
     public void printError(@ParameterAnnotation(" This is on the parameter Annotation")String error)
     {
         System.err.println(error);
     }

     @FieldAnnotation(" This is applied to the field Annotation")
     public int count;
 }

With Annotation, we can get the information, and we can get the Annotation in the following two ways. The first way is to read the Annotation information one by one by reflecting through the type, method and field. The second way is to read an Annotation of the specified type:

 read Annotation Methods a  
 private static void annotationTest1()
 {
     MyClass temp = new MyClass();

     Annotation[] annotations = temp.getClass().getAnnotations();
     for(Annotation a : annotations)
     {
         System.out.println(a.toString());
     }

     Method[] methods = temp.getClass().getDeclaredMethods();
     for(Method method : methods)
     {
         annotations = method.getAnnotations();
         for(Annotation a : annotations)
         {
             System.out.println(a.toString());
         }
         Annotation[][] paraAnnotations = method.getParameterAnnotations();
         for(int i = 0; i < paraAnnotations.length; i++)
         {
             for (Annotation a : paraAnnotations[i])
             {
                 System.out.println(a.toString());
             }
         }
     }

     Field[] fields = temp.getClass().getFields();
     for (Field field : fields)
     {
         annotations = field.getAnnotations();
         for(Annotation a : annotations)
         {
             System.out.println(a.toString());
         }
     }
 }


 read Annotation Way 2  
 private static void annotationTest2() throws ClassNotFoundException
 {
     Class classType = Class.forName("sample.reflection.annotation.MyClass");
     boolean flag = classType.isAnnotationPresent(ClassAnnotation.class);
     if (flag)
     {
         ClassAnnotation annotation = (ClassAnnotation) classType.getAnnotation(ClassAnnotation.class);
         System.out.println(annotation.toString());
     }
     Method[] methods = classType.getMethods();
     for(Method method : methods)
     {
         if (method.isAnnotationPresent(MethodAnnotation.class))
         {
             System.out.println(((MethodAnnotation)method.getAnnotation(MethodAnnotation.class)).toString());
         }
         Annotation[][] paraAnnotations = method.getParameterAnnotations();
         for(int i = 0; i < paraAnnotations.length; i++)
         {
             for (Annotation a : paraAnnotations[i])
             {
                 System.out.println(a.toString());
             }
         }
     }
     Field[] fields = classType.getFields();
     for (Field field:fields)
     {
         if (field.isAnnotationPresent(FieldAnnotation.class))
         {
             System.out.println(((FieldAnnotation)field.getAnnotation(FieldAnnotation.class)).toString());
         }
     }
 }

The output of the above two methods is the same, as follows:

@sample.reflection.annotation.ClassAnnotation(value= This is on the type Annotation)
@sample.reflection.annotation.MethodAnnotation(methodName=printInfo, returnType=void)
@sample.reflection.annotation.MethodAnnotation(methodName=printError, returnType=void)
@sample.reflection.annotation.ParameterAnnotation(value= This is on the parameter Annotation)
@sample.reflection.annotation.FieldAnnotation(value= This is applied to the field Annotation)

Use annotations in webservices
The code above might seem a bit boring, and it doesn't show the power of annotations, so let's look at webservices, where we can declare methods or parameters using WebMethod, WebParam, and other annotations.

Next, let's implement a very simple Web service:


@WebService(targetNamespace="http://test", serviceName="HelloService")
 public class HelloServiceProvider
 {
     @WebResult(name="HelloString")
     @WebMethod
     public String sayHello(@WebParam(name="userName") String name)
     {
         return "Hello " + name;
     }

     @Oneway
     @WebMethod(action="userLogin", operationName="userLogin")
     public void login()
     {
         System.out.println("User has logged on.");
     }

     public static void main(String[] args)
     {
         Thread thread = new Thread(new HelloServicePublisher());
         thread.start();
     }
 }

Then define a Publisher:

class HelloServicePublisher implements Runnable
 {
     public void run()
     {
         Endpoint.publish("http://localhost:8888/test/HelloService", new HelloServiceProvider());
     }
 }

On the command line, we locate the source code path and execute the following command:

wsgen -cp . HelloServiceProvider

Wsgen is located in the bin directory of the JDK.

Then we start HelloServiceProvider, enter the following address in your browser: http://localhost:8888/test/HelloService, you can see the following information:
< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201305/20130515154234.png ">
Click on the WSDL link to see:


WSDL information  
<!-- 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>

The JDK comes with a Web server, so we don't need to deploy the code to other servers.

Dynamic proxy mechanism
A big feature of Spring is AOP, and aspect-oriented programming is a trend in framework design. For common operations in the business, such as logging, maintaining transactions, and so on, if entangled with the business logic, it will lead to unclear code responsibilities and subsequent maintenance difficulties. With AOP, we can make a good separation between common and business operations.

Now let's implement a simple AOP framework that requires three parts: 1) InvocationHandler to trigger methods; 2) Interceptor, to define interceptors; 3) DynamicProxy to dynamically create proxy objects.

First let's look at the definition of Interptor:


interface AOPInterceptor
 {
     public void before(Method method, Object[] args);
     public void after(Method method, Object[] args);
     public void afterThrowing(Method method, Object[] args);
     public void afterFinally(Method method, Object[] args);
 }

Next up is InvocationHandler:

class DynamicProxyInvocationHandler implements InvocationHandler
 {
     private Object target;
     private AOPInterceptor interceptor;

     public DynamicProxyInvocationHandler(Object target, AOPInterceptor interceptor)
     {
         this.target = target;
         this.interceptor = interceptor;
     }

     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
     {
         try
         {
             interceptor.before(method, args);
             Object returnValue = method.invoke(target, args);
             interceptor.after(method, args);
             return returnValue;
         }
         catch(Throwable t)
         {
             interceptor.afterThrowing(method, args);
             throw t;
         }
         finally
         {
             interceptor.afterFinally(method, args);
         }
     }
 }

Finally, DynamicProxy:

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

At this point, we've built a "simple" AOP interceptor. Now let's create some test code.

The first is to implement the AOPInterceptor interface:


class MyInterceptor implements AOPInterceptor
 {

     public void after(Method method, Object[] args) {
         System.out.println(" Method execution ends. ");
     }

     public void afterFinally(Method method, Object[] args) {
         System.out.println(" Method body Finally Execution ended. ");
     }

     public void afterThrowing(Method method, Object[] args) {
         System.out.println(" Method throws an exception. ");
     }

     public void before(Method method, Object[] args) {
         System.out.println(" Method start execution ");
     }
 }

Then use the HelloWorldService defined at the beginning of this article to complete the test. The sayHello method of MyHello needs to be followed by a line of code at the end:

throw new RuntimeException();

Then the test code:

private static void test()
 {
     MyInterceptor interceptor = new MyInterceptor();
     HelloWorldService hello = new MyHelloWorld();
     DynamicProxyFactory factory = new DynamicProxyFactoryImpl();
     HelloWorldService proxy = factory.createProxy(HelloWorldService.class, hello, interceptor);
     proxy.sayHello("Zhang San");
 }

Finally, the implementation results are as follows:

 Method start execution 
Hello Zhang San.
 Method throws an exception. 
 Method body Finally Execution ended. 
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

As you can see, we have intercepted before, after, after the exception is thrown, and after the finally execution of the business to achieve the desired effect.


Related articles: