/* * Copyright 1999-2011 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.dubbo.common.bytecode; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.dubbo.common.utils.ClassHelper; import com.alibaba.dubbo.common.utils.ReflectUtils; /** * Proxy. * * @author qian.lei */ public abstract class Proxy { private static final AtomicLong PROXY_CLASS_COUNTER = new AtomicLong(0); private static final String PACKAGE_NAME = Proxy.class.getPackage().getName(); public static final InvocationHandler RETURN_NULL_INVOKER = new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args){ return null; } }; public static final InvocationHandler THROW_UNSUPPORTED_INVOKER = new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args){ throw new UnsupportedOperationException("Method [" + ReflectUtils.getName(method) + "] unimplemented."); } }; private static final Map<ClassLoader, Map<String, Object>> ProxyCacheMap = new WeakHashMap<ClassLoader, Map<String, Object>>(); private static final Object PendingGenerationMarker = new Object(); /** * Get proxy. * * @param ics interface class array. * @return Proxy instance. */ public static Proxy getProxy(Class<?>... ics) { return getProxy(ClassHelper.getCallerClassLoader(Proxy.class), ics); } /** * Get proxy. * @param cl class loader. * @param ics interface class array. * * @return Proxy instance. */ public static Proxy getProxy(ClassLoader cl, Class<?>... ics) { if( ics.length > 65535 ) throw new IllegalArgumentException("interface limit exceeded"); StringBuilder sb = new StringBuilder(); for(int i=0;i<ics.length;i++) { String itf = ics[i].getName(); if( !ics[i].isInterface() ) throw new RuntimeException(itf + " is not a interface."); Class<?> tmp = null; try { tmp = Class.forName(itf, false, cl); } catch(ClassNotFoundException e) {} if( tmp != ics[i] ) throw new IllegalArgumentException(ics[i] + " is not visible from class loader"); sb.append(itf).append(';'); } // use interface class name list as key. String key = sb.toString(); // get cache by class loader. Map<String, Object> cache; synchronized( ProxyCacheMap ) { cache = ProxyCacheMap.get(cl); if( cache == null ) { cache = new HashMap<String, Object>(); ProxyCacheMap.put(cl, cache); } } Proxy proxy = null; synchronized( cache ) { do { Object value = cache.get(key); if( value instanceof Reference<?> ) { proxy = (Proxy)((Reference<?>)value).get(); if( proxy != null ) return proxy; } if( value == PendingGenerationMarker ) { try{ cache.wait(); }catch(InterruptedException e){} } else { cache.put(key, PendingGenerationMarker); break; } } while( true ); } long id = PROXY_CLASS_COUNTER.getAndIncrement(); String pkg = null; ClassGenerator ccp = null, ccm = null; try { ccp = ClassGenerator.newInstance(cl); Set<String> worked = new HashSet<String>(); List<Method> methods = new ArrayList<Method>(); for(int i=0;i<ics.length;i++) { if( !Modifier.isPublic(ics[i].getModifiers()) ) { String npkg = ics[i].getPackage().getName(); if( pkg == null ) { pkg = npkg; } else { if( !pkg.equals(npkg) ) throw new IllegalArgumentException("non-public interfaces from different packages"); } } ccp.addInterface(ics[i]); for( Method method : ics[i].getMethods() ) { String desc = ReflectUtils.getDesc(method); if( worked.contains(desc) ) continue; worked.add(desc); int ix = methods.size(); Class<?> rt = method.getReturnType(); Class<?>[] pts = method.getParameterTypes(); StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];"); for(int j=0;j<pts.length;j++) code.append(" args[").append(j).append("] = ($w)$").append(j+1).append(";"); code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);"); if( !Void.TYPE.equals(rt) ) code.append(" return ").append(asArgument(rt, "ret")).append(";"); methods.add(method); ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString()); } } if( pkg == null ) pkg = PACKAGE_NAME; // create ProxyInstance class. String pcn = pkg + ".proxy" + id; ccp.setClassName(pcn); ccp.addField("public static java.lang.reflect.Method[] methods;"); ccp.addField("private " + InvocationHandler.class.getName() + " handler;"); ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{ InvocationHandler.class }, new Class<?>[0], "handler=$1;"); ccp.addDefaultConstructor(); Class<?> clazz = ccp.toClass(); clazz.getField("methods").set(null, methods.toArray(new Method[0])); // create Proxy class. String fcn = Proxy.class.getName() + id; ccm = ClassGenerator.newInstance(cl); ccm.setClassName(fcn); ccm.addDefaultConstructor(); ccm.setSuperClass(Proxy.class); ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }"); Class<?> pc = ccm.toClass(); proxy = (Proxy)pc.newInstance(); } catch(RuntimeException e) { throw e; } catch(Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { // release ClassGenerator if( ccp != null ) ccp.release(); if( ccm != null ) ccm.release(); synchronized( cache ) { if( proxy == null ) cache.remove(key); else cache.put(key, new WeakReference<Proxy>(proxy)); cache.notifyAll(); } } return proxy; } /** * get instance with default handler. * * @return instance. */ public Object newInstance() { return newInstance(THROW_UNSUPPORTED_INVOKER); } /** * get instance with special handler. * * @return instance. */ abstract public Object newInstance(InvocationHandler handler); protected Proxy(){} private static String asArgument(Class<?> cl, String name) { if( cl.isPrimitive() ) { if( Boolean.TYPE == cl ) return name + "==null?false:((Boolean)" + name + ").booleanValue()"; if( Byte.TYPE == cl ) return name + "==null?(byte)0:((Byte)" + name + ").byteValue()"; if( Character.TYPE == cl ) return name + "==null?(char)0:((Character)" + name + ").charValue()"; if( Double.TYPE == cl ) return name + "==null?(double)0:((Double)" + name + ").doubleValue()"; if( Float.TYPE == cl ) return name + "==null?(float)0:((Float)" + name + ").floatValue()"; if( Integer.TYPE == cl ) return name + "==null?(int)0:((Integer)" + name + ").intValue()"; if( Long.TYPE == cl ) return name + "==null?(long)0:((Long)" + name + ").longValue()"; if( Short.TYPE == cl ) return name + "==null?(short)0:((Short)" + name + ").shortValue()"; throw new RuntimeException(name+" is unknown primitive type."); } return "(" + ReflectUtils.getName(cl) + ")"+name; } }