0. Introduction
Weld and Reflection are not good friends. When you inject a bean into another, Weld CDI makes a proxy of the injected object. So when you want to obtain information using Reflection, you cannot see the original injected object.
BalusC gives us the clue to introspect a Weld proxy bean. He uses BeanInfo Interface from the Java Beans API.
By means of this Bean API, we can retrieve the original object from the proxy object. It is stored in the attribute targetInstance. Once we have this original object, we can use reflection.
Note: To use Weld with Tomcat, we must include this dependency in the pom.xml file
<!-- https://mvnrepository.com/artifact/org.jboss.weld.servlet/weld-servlet-shaded --> <dependency> <groupId>org.jboss.weld.servlet</groupId> <artifactId>weld-servlet-shaded</artifactId> <version>3.0.5.Final</version> </dependency>
1. A simple example
Let's consider 2 simple beans, Bean1 and Bean2, and Bean1 is injected into Bean2.Bean1.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package org.ximodante.jsf.provesalfred; import java.io.Serializable; import javax.enterprise.context.SessionScoped; import javax.inject.Named; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Named @ToString @SessionScoped @NoArgsConstructor public class Bean1 implements Serializable{ private static final long serialVersionUID = 1L; @Getter @Setter private String field1="a"; } |
Bean2.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | import java.io.Serializable; import javax.faces.view.ViewScoped; import javax.inject.Inject; import javax.inject.Named; import lombok.Getter; import lombok.Setter; import lombok.ToString; @Named @ToString @ViewScoped public class Bean2 implements Serializable{ private static final long serialVersionUID = 1L; @Inject @Getter @Setter private Bean1 b1; @Getter @Setter private String field2="33"; public String go() { System.out.println("HELLO!"); System.out.println("1. Displaying fields from beans"); System.out.println("==============================="); System.out.println("b1="+ b1.toString()); System.out.println("Bean2="+ this.toString()); System.out.println("\n2. Displaying class names "); System.out.println("==============================="); System.out.println("b1="+ b1.getClass().getName()); System.out.println("Bean2="+ this.getClass().getName()); System.out.println("\n3. Displaying information using Reflection API"); System.out.println("=============================================="); System.out.println("b1="+ ReflectionUtil.getObjectInfoReflx(b1)); System.out.println("b1="+ ReflectionUtil.getObjectInfoReflx(this)); System.out.println("\n4. Displaying information using Bean Info API"); System.out.println("=============================================="); System.out.println("b1="+ ReflectionUtil.getObjectInfoBean(b1)); System.out.println("b1="+ ReflectionUtil.getObjectInfoBean(this)); System.out.println("\n5. Displaying information using Both APIs"); System.out.println("=============================================="); System.out.println("b1="+ ReflectionUtil.getObjectInfo(b1)); System.out.println("b1="+ ReflectionUtil.getObjectInfo(this)); return null; } } |
Notice:
1. Bean1 is injected into Bean2 (line 15)
2. When displaying b1 class name (line 29) the name of the proxy class is displayed.
3. When displaying the fields using Reflection, (line 34) b1 fields are rather strange as it is a proxy.
4. When displaying the fields using Bean Info (line 39), a field called targetInstance is the underlying object.
5. With the new method getObjectInfo (line 44) that combines both API s>(Reflection and Bean) . we have solved the problem by means of using Reflection over the underlaying object when it is proxied.
ReflexionUtil.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | package org.ximodante.jsf.provesalfred; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; public class ReflectionUtil { /** * A class whose name contanins "$Proxy$", is a class injected and proxied by CDI * @param cls * @return */ public static boolean isProxy(Class<?> cls) { if (cls.getSimpleName().contains("Proxy$")) return true; else return false; } // Returns the underlying object proxied by CDI public static Object getProxiedObject(Object obj) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{ BeanInfo beanInfo; Object value=null; beanInfo = Introspector.getBeanInfo(obj.getClass()); PropertyDescriptor oProperty = Arrays.stream(beanInfo.getPropertyDescriptors()) .filter(e -> e.getName().equalsIgnoreCase("targetInstance")) .findFirst().get(); Method getter = oProperty.getReadMethod(); value = getter.invoke(obj); return value; } // Gets information from a class bypassing proxies public static String getObjectInfo(Object obj){ String info=""; Object myObj=obj; Class<?> cls=myObj.getClass(); try { //Obtain original class (if stored in a proxy) if (isProxy(cls)) { myObj=getProxiedObject(myObj); cls=myObj.getClass(); } info = getObjectInfoReflx(myObj); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | IntrospectionException e) { // TODO Auto-generated catch block e.printStackTrace(); } return info; } // Gets information from a class but cannot bypass proxies!!!!! public static String getObjectInfoReflx(Object obj) { String info=""; int i=0; try { info = info + "CLASS:" + obj.getClass().getName() + "\n"; info = info + "--------------------------------------------------\n"; for (Field f: obj.getClass().getDeclaredFields()){ f.setAccessible(true); if (f!=null) { info= info + " Field n." + i + "=" + f.getName() + " Type=" + f.getType(); if (f.get(obj) != null) { info= info + " Value=" + f.get(obj).toString().trim(); } i++; info=info + "\n"; } } } catch (Exception e) { e.printStackTrace(); } return info; } /** * Retrieves information from an object using Bean API * @param obj * @return */ public static String getObjectInfoBean(Object obj) { BeanInfo beanInfo; String info=""; try { info = info + "CLASS:" + obj.getClass().getName() + "\n"; info = info + "--------------------------------------------------\n"; beanInfo = Introspector.getBeanInfo(obj.getClass()); int i=0; for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) { String name = property.getName(); Method getter = property.getReadMethod(); Object value = getter.invoke(obj); String className=property.getPropertyType().getName(); info= info + " Field n." + i + "=" + name + " Type=" + value.getClass().getName(); if (value != null) { info= info + " Value=" + value.toString().trim(); } info=info + "\n"; i++; } } catch (Exception e) { e.printStackTrace(); } return info; } } |
Notice:
1. When a class is a proxy, its class name includes the string "Proxy$".
2. Notice how field information is retrieved in Reflection and Bean APIs.
3. How the field information is extracted taking into account if the class is proxied or not.
The xhtml page is
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:p="http://primefaces.org/ui"> <h:head> </h:head> <h:body> <h:form> <p:panel header="TEST CDI REFLECTION"> <h3>Reflection with CDI</h3> field1:<h:inputText value="#{bean1.field1}"/> <br /> field2:<h:inputText value="#{bean2.field2}"/> </p:panel> <h:commandButton value="Go" action="#{bean2.go}"/> </h:form> </h:body> </html> |
Look the window
And look carefully the results
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | HELLO! 1. Displaying fields from beans =============================== b1=Bean1(field1=a) Bean2=Bean2(b1=Bean1(field1=a), field2=33) 2. Displaying class names =============================== b1=org.ximodante.jsf.provesalfred.Bean1$Proxy$_$$_WeldClientProxy Bean2=org.ximodante.jsf.provesalfred.Bean2 3. Displaying information using Reflection API ============================================== b1=CLASS:org.ximodante.jsf.provesalfred.Bean1$Proxy$_$$_WeldClientProxy -------------------------------------------------- Field n.0=BEAN_ID_FIELD Type=interface org.jboss.weld.serialization.spi.BeanIdentifier Value=WELD%ManagedBean%STATIC_INSTANCE|/JSFv02_/WEB-INF/classes|org.ximodante.jsf.provesalfred.Bean1|null|false Field n.1=weld$$$53 Type=class java.lang.reflect.Method Value=public abstract void org.jboss.weld.interceptor.proxy.LifecycleMixin.lifecycle_mixin_$$_preDestroy() Field n.2=weld$$$52 Type=class java.lang.reflect.Method Value=public abstract void org.jboss.weld.interceptor.proxy.LifecycleMixin.lifecycle_mixin_$$_postConstruct() Field n.3=weld$$$55 Type=class java.lang.reflect.Method Value=public abstract java.lang.Class org.jboss.weld.interceptor.util.proxy.TargetInstanceProxy.getTargetClass() Field n.4=weld$$$54 Type=class java.lang.reflect.Method Value=public abstract java.lang.Object org.jboss.weld.interceptor.util.proxy.TargetInstanceProxy.getTargetInstance() Field n.5=methodHandler Type=class org.jboss.weld.bean.proxy.ProxyMethodHandler Value=org.jboss.weld.bean.proxy.ProxyMethodHandler@62ec8a98 Field n.6=constructed Type=boolean Value=true b1=CLASS:org.ximodante.jsf.provesalfred.Bean2 -------------------------------------------------- Field n.0=serialVersionUID Type=long Value=1 Field n.1=b1 Type=class org.ximodante.jsf.provesalfred.Bean1 Value=Bean1(field1=a) Field n.2=field2 Type=class java.lang.String Value=33 4. Displaying information using Bean Info API ============================================== b1=CLASS:org.ximodante.jsf.provesalfred.Bean1$Proxy$_$$_WeldClientProxy -------------------------------------------------- Field n.0=class Type=java.lang.Class Value=class org.ximodante.jsf.provesalfred.Bean1$Proxy$_$$_WeldClientProxy Field n.1=field1 Type=java.lang.String Value=a Field n.2=handler Type=org.jboss.weld.bean.proxy.ProxyMethodHandler Value=org.jboss.weld.bean.proxy.ProxyMethodHandler@62ec8a98 Field n.3=metadata Type=org.jboss.weld.bean.proxy.ProxyMethodHandler Value=org.jboss.weld.bean.proxy.ProxyMethodHandler@62ec8a98 Field n.4=targetClass Type=java.lang.Class Value=class org.ximodante.jsf.provesalfred.Bean1 Field n.5=targetInstance Type=org.ximodante.jsf.provesalfred.Bean1 Value=Bean1(field1=a) b1=CLASS:org.ximodante.jsf.provesalfred.Bean2 -------------------------------------------------- Field n.0=b1 Type=org.ximodante.jsf.provesalfred.Bean1$Proxy$_$$_WeldClientProxy Value=Bean1(field1=a) Field n.1=class Type=java.lang.Class Value=class org.ximodante.jsf.provesalfred.Bean2 Field n.2=field2 Type=java.lang.String Value=33 5. Displaying information using Both APIs ============================================== b1=CLASS:org.ximodante.jsf.provesalfred.Bean1 -------------------------------------------------- Field n.0=serialVersionUID Type=long Value=1 Field n.1=field1 Type=class java.lang.String Value=a b1=CLASS:org.ximodante.jsf.provesalfred.Bean2 -------------------------------------------------- Field n.0=serialVersionUID Type=long Value=1 Field n.1=b1 Type=class org.ximodante.jsf.provesalfred.Bean1 Value=Bean1(field1=a) Field n.2=field2 Type=class java.lang.String Value=33 |
Now we can use reflection on injected beans if we take into account these ideas.
Have a nice programming session!
Comments
Post a Comment