package org.hotswap.agent.plugin.spring.getbean; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.ProtectionDomain; import org.hotswap.agent.javassist.CannotCompileException; import org.hotswap.agent.javassist.ClassPool; import org.hotswap.agent.javassist.CtClass; import org.hotswap.agent.javassist.CtMethod; import org.hotswap.agent.javassist.CtNewMethod; import org.hotswap.agent.javassist.LoaderClassPath; import org.hotswap.agent.javassist.NotFoundException; import org.hotswap.agent.logging.AgentLogger; /** * Creates a Cglib proxy instance along with the neccessary Callback classes. Uses either the repackaged version of * Cglib (Spring >= 3.2) or the stand-alone version (Spring < 3.2). * * @author Erki Ehtla * */ public class EnhancerProxyCreater { private static AgentLogger LOGGER = AgentLogger.getLogger(EnhancerProxyCreater.class); private static EnhancerProxyCreater INSTANCE; public static final String SPRING_PACKAGE = "org.springframework.cglib."; public static final String CGLIB_PACKAGE = "net.sf.cglib."; private Class<?> springProxy; private Class<?> springCallback; private Class<?> springNamingPolicy; private Method createSpringProxy; private Class<?> cglibProxy; private Class<?> cglibCallback; private Class<?> cglibNamingPolicy; private Method createCglibProxy; private Object springLock = new Object(); private Object cglibLock = new Object(); private final ClassLoader loader; private final ProtectionDomain pd; public EnhancerProxyCreater(ClassLoader loader, ProtectionDomain pd) { super(); this.loader = loader; this.pd = pd; } public static boolean isSupportedCglibProxy(Object bean) { if (bean == null) { return false; } String beanClassName = bean.getClass().getName(); return beanClassName.contains("$$EnhancerBySpringCGLIB") || beanClassName.contains("$$EnhancerByCGLIB"); } /** * Creates a Cglib proxy instance along with the neccessary Callback classes, if those have not been created * already. Uses either the repackaged version of Cglib (Spring >= 3.2) or the stand-alone version (Spring < 3.2). * * @param beanFactry * Spring beanFactory * @param bean * Spring bean * @param paramClasses * Parameter Classes of the Spring beanFactory method which returned the bean. The method is named * ProxyReplacer.FACTORY_METHOD_NAME * @param paramValues * Parameter values of the Spring beanFactory method which returned the bean. The method is named * ProxyReplacer.FACTORY_METHOD_NAME * @return */ public static Object createProxy(Object beanFactry, Object bean, Class<?>[] paramClasses, Object[] paramValues) { if (INSTANCE == null) { INSTANCE = new EnhancerProxyCreater(bean.getClass().getClassLoader(), bean.getClass().getProtectionDomain()); } return INSTANCE.create(beanFactry, bean, paramClasses, paramValues); } private Object create(Object beanFactry, Object bean, Class<?>[] paramClasses, Object[] paramValues) { try { Method proxyCreater = getProxyCreationMethod(bean); if (proxyCreater == null) { return bean; } return proxyCreater.invoke(null, beanFactry, bean, paramClasses, paramValues); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | CannotCompileException | NotFoundException e) { LOGGER.error("Creating a proxy failed", e); throw new RuntimeException(e); } } private Method getProxyCreationMethod(Object bean) throws CannotCompileException, NotFoundException { if (getCp(loader).find("org.springframework.cglib.proxy.MethodInterceptor") != null) { if (createSpringProxy == null) { synchronized (springLock) { if (createSpringProxy == null) { ClassPool cp = getCp(loader); springCallback = buildProxyCallbackClass(SPRING_PACKAGE, cp); springNamingPolicy = buildNamingPolicyClass(SPRING_PACKAGE, cp); springProxy = buildProxyCreaterClass(SPRING_PACKAGE, springCallback, springNamingPolicy, cp); createSpringProxy = springProxy.getDeclaredMethods()[0]; } } } return createSpringProxy; } else if (getCp(loader).find("net.sf.cglib.proxy.MethodInterceptor") != null) { if (createCglibProxy == null) { synchronized (cglibLock) { if (createCglibProxy == null) { ClassPool cp = getCp(loader); cglibCallback = buildProxyCallbackClass(CGLIB_PACKAGE, cp); cglibNamingPolicy = buildNamingPolicyClass(CGLIB_PACKAGE, cp); cglibProxy = buildProxyCreaterClass(CGLIB_PACKAGE, cglibCallback, cglibNamingPolicy, cp); createCglibProxy = cglibProxy.getDeclaredMethods()[0]; } } } return createCglibProxy; } else { LOGGER.error("Unable to determine the location of the Cglib package"); return null; } } /** * Builds a class that has a single public static method create(Object beanFactry, Object bean, Class[] classes, * Object[] params). The method of the created class returns a Cglib Enhancer created proxy of the parameter bean. * The proxy has single callback, whish is a subclass of DetachableBeanHolder. Classname prefix for created proxies * will be HOTSWAPAGENT_ * * @param cglibPackage * Cglib Package name * @param callback * Callback class used for Enhancer * @param namingPolicy * NamingPolicy class used for Enhancer * @param cp * @return Class that creates proxies via method "public static Object create(Object beanFactry, Object bean, * Class[] classes, Object[] params)" * @throws CannotCompileException */ private Class<?> buildProxyCreaterClass(String cglibPackage, Class<?> callback, Class<?> namingPolicy, ClassPool cp) throws CannotCompileException { CtClass ct = cp.makeClass("HotswapAgentSpringBeanProxy" + getClassSuffix(cglibPackage)); String proxy = cglibPackage + "proxy."; String core = cglibPackage + "core."; String rawBody = "public static Object create(Object beanFactry, Object bean, Class[] classes, Object[] params) {" + "{2} handler = new {2}(bean, beanFactry, classes, params);"// + " {0}Enhancer e = new {0}Enhancer();"// + " e.setUseCache(true);"// + " Class[] proxyInterfaces = new Class[bean.getClass().getInterfaces().length];"// + " Class[] classInterfaces = bean.getClass().getInterfaces();"// + " for (int i = 0; i < classInterfaces.length; i++) {"// + " proxyInterfaces[i] = classInterfaces[i];"// + " }"// + " e.setInterfaces(proxyInterfaces);"// + " e.setSuperclass(bean.getClass().getSuperclass());"// + " e.setCallback(handler);"// + " e.setCallbackType({2}.class);"// + " e.setNamingPolicy(new {3}());"// + " return e.create();"// + " }"; String body = rawBody.replaceAll("\\{0\\}", proxy).replaceAll("\\{1\\}", core) .replaceAll("\\{2\\}", callback.getName()).replaceAll("\\{3\\}", namingPolicy.getName()); CtMethod m = CtNewMethod.make(body, ct); ct.addMethod(m); return ct.toClass(loader, pd); } /** * Creates a NamingPolicy for usage in buildProxyCreaterClass. Eventually a instance of this class will be used as * an argument for an Enhancer instances setNamingPolicy method. Classname prefix for proxies will be HOTSWAPAGENT_ * * @param cglibPackage * Cglib Package name * @param cp * @return DefaultNamingPolicy sublass * @throws CannotCompileException * @throws NotFoundException */ private Class<?> buildNamingPolicyClass(String cglibPackage, ClassPool cp) throws CannotCompileException, NotFoundException { CtClass ct = cp.makeClass("HotswapAgentSpringNamingPolicy" + getClassSuffix(cglibPackage)); String core = cglibPackage + "core."; String originalNamingPolicy = core + "SpringNamingPolicy"; if (cp.find(originalNamingPolicy) == null) originalNamingPolicy = core + "DefaultNamingPolicy"; ct.setSuperclass(cp.get(originalNamingPolicy)); String rawBody = " public String getClassName(String prefix, String source, Object key, {0}Predicate names) {"// + " return super.getClassName(\"HOTSWAPAGENT_\" + prefix, source, key, names);"// + " }"; String body = rawBody.replaceAll("\\{0\\}", core); CtMethod m = CtNewMethod.make(body, ct); ct.addMethod(m); return ct.toClass(loader, pd); } private static String getClassSuffix(String cglibPackage) { return String.valueOf(cglibPackage.hashCode()).replace("-", "_"); } /** * Creates a Cglib Callback which is a subclass of DetachableBeanHolder * * @param cglibPackage * Cglib Package name * @param cp * @return Class of the Enhancer Proxy callback * @throws CannotCompileException * @throws NotFoundException */ private Class<?> buildProxyCallbackClass(String cglibPackage, ClassPool cp) throws CannotCompileException, NotFoundException { String proxyPackage = cglibPackage + "proxy."; CtClass ct = cp.makeClass("HotswapSpringCallback" + getClassSuffix(cglibPackage)); ct.setSuperclass(cp.get(DetachableBeanHolder.class.getName())); ct.addInterface(cp.get(proxyPackage + "MethodInterceptor")); String rawBody = " public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, {0}MethodProxy proxy) throws Throwable {"// + " if(method != null && method.getName().equals(\"finalize\") && method.getParameterTypes().length == 0)" // + " return null;" // + " return proxy.invoke(getBean(), args);" // + " }"; String body = rawBody.replaceAll("\\{0\\}", proxyPackage); CtMethod m = CtNewMethod.make(body, ct); ct.addMethod(m); return ct.toClass(loader, pd); } private ClassPool getCp(ClassLoader loader) { ClassPool cp = new ClassPool(); cp.appendSystemPath(); cp.appendClassPath(new LoaderClassPath(loader)); return cp; } }