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