• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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