1 /** 2 * Copyright (C) 2009 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.inject.internal; 18 19 import static com.google.inject.internal.BytecodeGen.newFastClass; 20 21 import com.google.common.collect.ImmutableList; 22 import com.google.common.collect.ImmutableMap; 23 import com.google.common.collect.ImmutableSet; 24 import com.google.common.collect.Lists; 25 import com.google.common.collect.Maps; 26 import com.google.inject.spi.InjectionPoint; 27 28 import net.sf.cglib.core.MethodWrapper; 29 import net.sf.cglib.proxy.Callback; 30 import net.sf.cglib.proxy.CallbackFilter; 31 import net.sf.cglib.proxy.Enhancer; 32 import net.sf.cglib.reflect.FastClass; 33 import net.sf.cglib.reflect.FastConstructor; 34 35 import org.aopalliance.intercept.MethodInterceptor; 36 37 import java.lang.reflect.Constructor; 38 import java.lang.reflect.InvocationTargetException; 39 import java.lang.reflect.Method; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.logging.Level; 43 import java.util.logging.Logger; 44 45 /** 46 * Builds a construction proxy that can participate in AOP. This class manages applying type and 47 * method matchers to come up with the set of intercepted methods. 48 * 49 * @author jessewilson@google.com (Jesse Wilson) 50 */ 51 final class ProxyFactory<T> implements ConstructionProxyFactory<T> { 52 53 private static final Logger logger = Logger.getLogger(ProxyFactory.class.getName()); 54 55 private final InjectionPoint injectionPoint; 56 private final ImmutableMap<Method, List<MethodInterceptor>> interceptors; 57 private final Class<T> declaringClass; 58 private final List<Method> methods; 59 private final Callback[] callbacks; 60 61 /** 62 * PUBLIC is default; it's used if all the methods we're intercepting are public. This impacts 63 * which classloader we should use for loading the enhanced class 64 */ 65 private BytecodeGen.Visibility visibility = BytecodeGen.Visibility.PUBLIC; 66 ProxyFactory(InjectionPoint injectionPoint, Iterable<MethodAspect> methodAspects)67 ProxyFactory(InjectionPoint injectionPoint, Iterable<MethodAspect> methodAspects) { 68 this.injectionPoint = injectionPoint; 69 70 @SuppressWarnings("unchecked") // the member of injectionPoint is always a Constructor<T> 71 Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember(); 72 declaringClass = constructor.getDeclaringClass(); 73 74 // Find applicable aspects. Bow out if none are applicable to this class. 75 List<MethodAspect> applicableAspects = Lists.newArrayList(); 76 for (MethodAspect methodAspect : methodAspects) { 77 if (methodAspect.matches(declaringClass)) { 78 applicableAspects.add(methodAspect); 79 } 80 } 81 82 if (applicableAspects.isEmpty()) { 83 interceptors = ImmutableMap.of(); 84 methods = ImmutableList.of(); 85 callbacks = null; 86 return; 87 } 88 89 // Get list of methods from cglib. 90 methods = Lists.newArrayList(); 91 Enhancer.getMethods(declaringClass, null, methods); 92 93 // Create method/interceptor holders and record indices. 94 List<MethodInterceptorsPair> methodInterceptorsPairs = Lists.newArrayList(); 95 for (Method method : methods) { 96 methodInterceptorsPairs.add(new MethodInterceptorsPair(method)); 97 } 98 99 // Iterate over aspects and add interceptors for the methods they apply to 100 boolean anyMatched = false; 101 for (MethodAspect methodAspect : applicableAspects) { 102 for (MethodInterceptorsPair pair : methodInterceptorsPairs) { 103 if (methodAspect.matches(pair.method)) { 104 if(pair.method.isSynthetic()) { 105 logger.log(Level.WARNING, 106 "Method [{0}] is synthetic and is being intercepted by {1}." 107 + " This could indicate a bug. The method may be intercepted twice," 108 + " or may not be intercepted at all.", 109 new Object[] { pair.method, methodAspect.interceptors() }); 110 } 111 visibility = visibility.and(BytecodeGen.Visibility.forMember(pair.method)); 112 pair.addAll(methodAspect.interceptors()); 113 anyMatched = true; 114 } 115 } 116 } 117 118 if (!anyMatched) { 119 interceptors = ImmutableMap.of(); 120 callbacks = null; 121 return; 122 } 123 124 ImmutableMap.Builder<Method, List<MethodInterceptor>> interceptorsMapBuilder = null; // lazy 125 126 callbacks = new Callback[methods.size()]; 127 for (int i = 0; i < methods.size(); i++) { 128 MethodInterceptorsPair pair = methodInterceptorsPairs.get(i); 129 130 if (!pair.hasInterceptors()) { 131 callbacks[i] = net.sf.cglib.proxy.NoOp.INSTANCE; 132 continue; 133 } 134 135 if (interceptorsMapBuilder == null) { 136 interceptorsMapBuilder = ImmutableMap.builder(); 137 } 138 139 ImmutableList<MethodInterceptor> deDuplicated = 140 ImmutableSet.copyOf(pair.interceptors).asList(); 141 interceptorsMapBuilder.put(pair.method, deDuplicated); 142 callbacks[i] = new InterceptorStackCallback(pair.method, deDuplicated); 143 } 144 145 interceptors = interceptorsMapBuilder != null 146 ? interceptorsMapBuilder.build() 147 : ImmutableMap.<Method, List<MethodInterceptor>>of(); 148 } 149 150 /** 151 * Returns the interceptors that apply to the constructed type. 152 */ getInterceptors()153 public ImmutableMap<Method, List<MethodInterceptor>> getInterceptors() { 154 return interceptors; 155 } 156 create()157 public ConstructionProxy<T> create() throws ErrorsException { 158 if (interceptors.isEmpty()) { 159 return new DefaultConstructionProxyFactory<T>(injectionPoint).create(); 160 } 161 162 @SuppressWarnings("unchecked") 163 Class<? extends Callback>[] callbackTypes = new Class[callbacks.length]; 164 for (int i = 0; i < callbacks.length; i++) { 165 if (callbacks[i] == net.sf.cglib.proxy.NoOp.INSTANCE) { 166 callbackTypes[i] = net.sf.cglib.proxy.NoOp.class; 167 } else { 168 callbackTypes[i] = net.sf.cglib.proxy.MethodInterceptor.class; 169 } 170 } 171 172 // Create the proxied class. We're careful to ensure that all enhancer state is not-specific 173 // to this injector. Otherwise, the proxies for each injector will waste PermGen memory 174 try { 175 Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility); 176 enhancer.setCallbackFilter(new IndicesCallbackFilter(methods)); 177 enhancer.setCallbackTypes(callbackTypes); 178 return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors); 179 } catch (Throwable e) { 180 throw new Errors().errorEnhancingClass(declaringClass, e).toException(); 181 } 182 } 183 184 private static class MethodInterceptorsPair { 185 final Method method; 186 List<MethodInterceptor> interceptors; // lazy 187 MethodInterceptorsPair(Method method)188 MethodInterceptorsPair(Method method) { 189 this.method = method; 190 } 191 addAll(List<MethodInterceptor> interceptors)192 void addAll(List<MethodInterceptor> interceptors) { 193 if (this.interceptors == null) { 194 this.interceptors = Lists.newArrayList(); 195 } 196 this.interceptors.addAll(interceptors); 197 } 198 hasInterceptors()199 boolean hasInterceptors() { 200 return interceptors != null; 201 } 202 } 203 204 /** 205 * A callback filter that maps methods to unique IDs. We define equals and 206 * hashCode without using any state related to the injector so that enhanced 207 * classes intercepting the same methods can be shared between injectors (and 208 * child injectors, etc). 209 */ 210 private static class IndicesCallbackFilter implements CallbackFilter { 211 final Map<Object, Integer> indices; 212 final int hashCode; 213 IndicesCallbackFilter(List<Method> methods)214 IndicesCallbackFilter(List<Method> methods) { 215 final Map<Object, Integer> indices = Maps.newHashMap(); 216 for (int i = 0; i < methods.size(); i++) { 217 indices.put(MethodWrapper.create(methods.get(i)), i); 218 } 219 this.indices = indices; 220 this.hashCode = indices.hashCode(); 221 } 222 accept(Method method)223 public int accept(Method method) { 224 return indices.get(MethodWrapper.create(method)); 225 } 226 equals(Object o)227 @Override public boolean equals(Object o) { 228 return o instanceof IndicesCallbackFilter && 229 ((IndicesCallbackFilter) o).indices.equals(indices); 230 } 231 hashCode()232 @Override public int hashCode() { 233 return hashCode; 234 } 235 } 236 237 /** 238 * Constructs instances that participate in AOP. 239 */ 240 private static class ProxyConstructor<T> implements ConstructionProxy<T> { 241 final Class<?> enhanced; 242 final InjectionPoint injectionPoint; 243 final Constructor<T> constructor; 244 final Callback[] callbacks; 245 246 final FastConstructor fastConstructor; 247 final ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors; 248 249 @SuppressWarnings("unchecked") // the constructor promises to construct 'T's ProxyConstructor(Enhancer enhancer, InjectionPoint injectionPoint, Callback[] callbacks, ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors)250 ProxyConstructor(Enhancer enhancer, InjectionPoint injectionPoint, Callback[] callbacks, 251 ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors) { 252 this.enhanced = enhancer.createClass(); // this returns a cached class if possible 253 this.injectionPoint = injectionPoint; 254 this.constructor = (Constructor<T>) injectionPoint.getMember(); 255 this.callbacks = callbacks; 256 this.methodInterceptors = methodInterceptors; 257 258 FastClass fastClass = newFastClass(enhanced, BytecodeGen.Visibility.forMember(constructor)); 259 this.fastConstructor = fastClass.getConstructor(constructor.getParameterTypes()); 260 } 261 262 @SuppressWarnings("unchecked") // the constructor promises to produce 'T's newInstance(Object... arguments)263 public T newInstance(Object... arguments) throws InvocationTargetException { 264 Enhancer.registerCallbacks(enhanced, callbacks); 265 try { 266 return (T) fastConstructor.newInstance(arguments); 267 } finally { 268 Enhancer.registerCallbacks(enhanced, null); 269 } 270 } 271 getInjectionPoint()272 public InjectionPoint getInjectionPoint() { 273 return injectionPoint; 274 } 275 getConstructor()276 public Constructor<T> getConstructor() { 277 return constructor; 278 } 279 getMethodInterceptors()280 public ImmutableMap<Method, List<MethodInterceptor>> getMethodInterceptors() { 281 return methodInterceptors; 282 } 283 } 284 } 285