View Javadoc
1   /*
2    *    Copyright 2009-2023 the original author or authors.
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    *       https://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  package org.apache.ibatis.executor.loader.javassist;
17  
18  import java.lang.reflect.Method;
19  import java.util.List;
20  import java.util.Map;
21  import java.util.Set;
22  
23  import javassist.util.proxy.MethodHandler;
24  import javassist.util.proxy.Proxy;
25  import javassist.util.proxy.ProxyFactory;
26  
27  import org.apache.ibatis.executor.ExecutorException;
28  import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy;
29  import org.apache.ibatis.executor.loader.AbstractSerialStateHolder;
30  import org.apache.ibatis.executor.loader.ResultLoaderMap;
31  import org.apache.ibatis.executor.loader.WriteReplaceInterface;
32  import org.apache.ibatis.io.Resources;
33  import org.apache.ibatis.logging.Log;
34  import org.apache.ibatis.logging.LogFactory;
35  import org.apache.ibatis.reflection.ExceptionUtil;
36  import org.apache.ibatis.reflection.factory.ObjectFactory;
37  import org.apache.ibatis.reflection.property.PropertyCopier;
38  import org.apache.ibatis.reflection.property.PropertyNamer;
39  import org.apache.ibatis.session.Configuration;
40  
41  /**
42   * @author Eduardo Macarron
43   */
44  public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {
45  
46    private static final String FINALIZE_METHOD = "finalize";
47    private static final String WRITE_REPLACE_METHOD = "writeReplace";
48  
49    public JavassistProxyFactory() {
50      try {
51        Resources.classForName("javassist.util.proxy.ProxyFactory");
52      } catch (Throwable e) {
53        throw new IllegalStateException(
54            "Cannot enable lazy loading because Javassist is not available. Add Javassist to your classpath.", e);
55      }
56    }
57  
58    @Override
59    public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,
60        ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
61      return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory,
62          constructorArgTypes, constructorArgs);
63    }
64  
65    public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties,
66        ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
67      return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes,
68          constructorArgs);
69    }
70  
71    static Object createStaticProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes,
72        List<Object> constructorArgs) {
73  
74      ProxyFactory enhancer = new ProxyFactory();
75      enhancer.setSuperclass(type);
76  
77      try {
78        type.getDeclaredMethod(WRITE_REPLACE_METHOD);
79        // ObjectOutputStream will call writeReplace of objects returned by writeReplace
80        if (LogHolder.log.isDebugEnabled()) {
81          LogHolder.log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
82        }
83      } catch (NoSuchMethodException e) {
84        enhancer.setInterfaces(new Class[] { WriteReplaceInterface.class });
85      } catch (SecurityException e) {
86        // nothing to do here
87      }
88  
89      Object enhanced;
90      Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
91      Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
92      try {
93        enhanced = enhancer.create(typesArray, valuesArray);
94      } catch (Exception e) {
95        throw new ExecutorException("Error creating lazy proxy.  Cause: " + e, e);
96      }
97      ((Proxy) enhanced).setHandler(callback);
98      return enhanced;
99    }
100 
101   private static class EnhancedResultObjectProxyImpl implements MethodHandler {
102 
103     private final Class<?> type;
104     private final ResultLoaderMap lazyLoader;
105     private final boolean aggressive;
106     private final Set<String> lazyLoadTriggerMethods;
107     private final ObjectFactory objectFactory;
108     private final List<Class<?>> constructorArgTypes;
109     private final List<Object> constructorArgs;
110 
111     private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration,
112         ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
113       this.type = type;
114       this.lazyLoader = lazyLoader;
115       this.aggressive = configuration.isAggressiveLazyLoading();
116       this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
117       this.objectFactory = objectFactory;
118       this.constructorArgTypes = constructorArgTypes;
119       this.constructorArgs = constructorArgs;
120     }
121 
122     public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,
123         ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
124       final Class<?> type = target.getClass();
125       EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration,
126           objectFactory, constructorArgTypes, constructorArgs);
127       Object enhanced = createStaticProxy(type, callback, constructorArgTypes, constructorArgs);
128       PropertyCopier.copyBeanProperties(type, target, enhanced);
129       return enhanced;
130     }
131 
132     @Override
133     public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
134       final String methodName = method.getName();
135       try {
136         synchronized (lazyLoader) {
137           if (WRITE_REPLACE_METHOD.equals(methodName)) {
138             Object original;
139             if (constructorArgTypes.isEmpty()) {
140               original = objectFactory.create(type);
141             } else {
142               original = objectFactory.create(type, constructorArgTypes, constructorArgs);
143             }
144             PropertyCopier.copyBeanProperties(type, enhanced, original);
145             if (lazyLoader.size() > 0) {
146               return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory,
147                   constructorArgTypes, constructorArgs);
148             } else {
149               return original;
150             }
151           }
152           if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
153             if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
154               lazyLoader.loadAll();
155             } else if (PropertyNamer.isSetter(methodName)) {
156               final String property = PropertyNamer.methodToProperty(methodName);
157               lazyLoader.remove(property);
158             } else if (PropertyNamer.isGetter(methodName)) {
159               final String property = PropertyNamer.methodToProperty(methodName);
160               if (lazyLoader.hasLoader(property)) {
161                 lazyLoader.load(property);
162               }
163             }
164           }
165         }
166         return methodProxy.invoke(enhanced, args);
167       } catch (Throwable t) {
168         throw ExceptionUtil.unwrapThrowable(t);
169       }
170     }
171   }
172 
173   private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy
174       implements MethodHandler {
175 
176     private EnhancedDeserializationProxyImpl(Class<?> type, Map<String, ResultLoaderMap.LoadPair> unloadedProperties,
177         ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
178       super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
179     }
180 
181     public static Object createProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties,
182         ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
183       final Class<?> type = target.getClass();
184       EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties,
185           objectFactory, constructorArgTypes, constructorArgs);
186       Object enhanced = createStaticProxy(type, callback, constructorArgTypes, constructorArgs);
187       PropertyCopier.copyBeanProperties(type, target, enhanced);
188       return enhanced;
189     }
190 
191     @Override
192     public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
193       final Object o = super.invoke(enhanced, method, args);
194       return o instanceof AbstractSerialStateHolder ? o : methodProxy.invoke(o, args);
195     }
196 
197     @Override
198     protected AbstractSerialStateHolder newSerialStateHolder(Object userBean,
199         Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
200         List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
201       return new JavassistSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes,
202           constructorArgs);
203     }
204   }
205 
206   private static class LogHolder {
207     private static final Log log = LogFactory.getLog(JavassistProxyFactory.class);
208   }
209 
210 }