com.example.post.MyConfigurationClassEnhancer.java Source code

Java tutorial

Introduction

Here is the source code for com.example.post.MyConfigurationClassEnhancer.java

Source

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * 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.example.post;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.asm.Type;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
import org.springframework.cglib.core.ClassGenerator;
import org.springframework.cglib.core.Constants;
import org.springframework.cglib.core.DefaultGeneratorStrategy;
import org.springframework.cglib.core.SpringNamingPolicy;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.CallbackFilter;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.cglib.transform.ClassEmitterTransformer;
import org.springframework.cglib.transform.TransformingClassGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.objenesis.ObjenesisException;
import org.springframework.objenesis.SpringObjenesis;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

/**
 * Enhances {@link Configuration} classes by generating a CGLIB subclass which
 * interacts with the Spring container to respect bean scoping semantics for
 * {@code @Bean} methods. Each such {@code @Bean} method will be overridden in
 * the generated subclass, only delegating to the actual {@code @Bean} method
 * implementation if the container actually requests the construction of a new
 * instance. Otherwise, a call to such an {@code @Bean} method serves as a
 * reference back to the container, obtaining the corresponding bean by name.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.0
 * @see #enhance
 * @see ConfigurationClassPostProcessor
 */
public class MyConfigurationClassEnhancer {

    // The callbacks to use. Note that these callbacks must be stateless.
    private static final Callback[] CALLBACKS = new Callback[] { new BeanMethodInterceptor(),
            //new BeanFactoryAwareMethodInterceptor(),
            NoOp.INSTANCE };

    private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);

    private static final String BEAN_FACTORY_FIELD = "$$beanFactory";

    private static final Log logger = LogFactory.getLog(MyConfigurationClassEnhancer.class);

    private static final SpringObjenesis objenesis = new SpringObjenesis();

    /**
     * Loads the specified class and generates a CGLIB subclass of it equipped with
     * container-aware callbacks capable of respecting scoping and other bean semantics.
     * @return the enhanced subclass
     */
    public Class<?> enhance(Class<?> configClass, ClassLoader classLoader) {
        if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
            if (logger.isDebugEnabled()) {
                logger.debug(String.format(
                        "Ignoring request to enhance %s as it has "
                                + "already been enhanced. This usually indicates that more than one "
                                + "ConfigurationClassPostProcessor has been registered (e.g. via "
                                + "<context:annotation-config>). This is harmless, but you may "
                                + "want check your configuration and remove one CCPP if possible",
                        configClass.getName()));
            }
            return configClass;
        }
        Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s",
                    configClass.getName(), enhancedClass.getName()));
        }
        return enhancedClass;
    }

    /**
     * Creates a new CGLIB {@link Enhancer} instance.
     */
    private Enhancer newEnhancer(Class<?> superclass, ClassLoader classLoader) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(superclass);
        //enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
        enhancer.setUseFactory(false);
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        //enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
        enhancer.setCallbackFilter(CALLBACK_FILTER);
        enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
        return enhancer;
    }

    /**
     * Uses enhancer to generate a subclass of superclass,
     * ensuring that callbacks are registered for the new subclass.
     */
    private Class<?> createClass(Enhancer enhancer) {
        Class<?> subclass = enhancer.createClass();
        // Registering callbacks statically (as opposed to thread-local)
        // is critical for usage in an OSGi environment (SPR-5932)...
        Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
        return subclass;
    }

    /**
     * Marker interface to be implemented by all @Configuration CGLIB subclasses.
     * Facilitates idempotent behavior for {@link MyConfigurationClassEnhancer#enhance}
     * through checking to see if candidate classes are already assignable to it, e.g.
     * have already been enhanced.
     * <p>Also extends {@link BeanFactoryAware}, as all enhanced {@code @Configuration}
     * classes require access to the {@link BeanFactory} that created them.
     * <p>Note that this interface is intended for framework-internal use only, however
     * must remain public in order to allow access to subclasses generated from other
     * packages (i.e. user code).
     */
    public interface EnhancedConfiguration extends BeanFactoryAware {
    }

    /**
     * Conditional {@link Callback}.
     * @see ConditionalCallbackFilter
     */
    private static interface ConditionalCallback extends Callback {

        boolean isMatch(Method candidateMethod);
    }

    /**
     * A {@link CallbackFilter} that works by interrogating {@link Callback}s in the order
     * that they are defined via {@link ConditionalCallback}.
     */
    private static class ConditionalCallbackFilter implements CallbackFilter {

        private final Callback[] callbacks;

        private final Class<?>[] callbackTypes;

        public ConditionalCallbackFilter(Callback[] callbacks) {
            this.callbacks = callbacks;
            this.callbackTypes = new Class<?>[callbacks.length];
            for (int i = 0; i < callbacks.length; i++) {
                this.callbackTypes[i] = callbacks[i].getClass();
            }
        }

        @Override
        public int accept(Method method) {
            for (int i = 0; i < this.callbacks.length; i++) {
                if (!(this.callbacks[i] instanceof ConditionalCallback)
                        || ((ConditionalCallback) this.callbacks[i]).isMatch(method)) {
                    return i;
                }
            }
            throw new IllegalStateException("No callback available for method " + method.getName());
        }

        public Class<?>[] getCallbackTypes() {
            return this.callbackTypes;
        }
    }

    /**
     * Custom extension of CGLIB's DefaultGeneratorStrategy, introducing a {@link BeanFactory} field.
     * Also exposes the application ClassLoader as thread context ClassLoader for the time of
     * class generation (in order for ASM to pick it up when doing common superclass resolution).
     */
    private static class BeanFactoryAwareGeneratorStrategy extends DefaultGeneratorStrategy {

        private final ClassLoader classLoader;

        public BeanFactoryAwareGeneratorStrategy(ClassLoader classLoader) {
            this.classLoader = classLoader;
        }

        @Override
        protected ClassGenerator transform(ClassGenerator cg) throws Exception {
            ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
                @Override
                public void end_class() {
                    declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
                    super.end_class();
                }
            };
            return new TransformingClassGenerator(cg, transformer);
        }

        @Override
        public byte[] generate(ClassGenerator cg) throws Exception {
            if (this.classLoader == null) {
                return super.generate(cg);
            }

            Thread currentThread = Thread.currentThread();
            ClassLoader threadContextClassLoader;
            try {
                threadContextClassLoader = currentThread.getContextClassLoader();
            } catch (Throwable ex) {
                // Cannot access thread context ClassLoader - falling back...
                return super.generate(cg);
            }

            boolean overrideClassLoader = !this.classLoader.equals(threadContextClassLoader);
            if (overrideClassLoader) {
                currentThread.setContextClassLoader(this.classLoader);
            }
            try {
                return super.generate(cg);
            } finally {
                if (overrideClassLoader) {
                    // Reset original thread context ClassLoader.
                    currentThread.setContextClassLoader(threadContextClassLoader);
                }
            }
        }
    }

    /**
     * Intercepts the invocation of any {@link BeanFactoryAware#setBeanFactory(BeanFactory)} on
     * {@code @Configuration} class instances for the purpose of recording the {@link BeanFactory}.
     * @see EnhancedConfiguration
     */
    private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            Field field = obj.getClass().getDeclaredField(BEAN_FACTORY_FIELD);
            Assert.state(field != null, "Unable to find generated BeanFactory field");
            field.set(obj, args[0]);

            // Does the actual (non-CGLIB) superclass actually implement BeanFactoryAware?
            // If so, call its setBeanFactory() method. If not, just exit.
            if (BeanFactoryAware.class.isAssignableFrom(obj.getClass().getSuperclass())) {
                return proxy.invokeSuper(obj, args);
            }
            return null;
        }

        @Override
        public boolean isMatch(Method candidateMethod) {
            return (candidateMethod.getName().equals("setBeanFactory")
                    && candidateMethod.getParameterTypes().length == 1
                    && BeanFactory.class == candidateMethod.getParameterTypes()[0]
                    && BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
        }
    }

    /**
     * Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper
     * handling of bean semantics such as scoping and AOP proxying.
     * @see Bean
     * @see MyConfigurationClassEnhancer
     */
    private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

        /**
         * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
         * existence of this bean object.
         * @throws Throwable as a catch-all for any exception that may be thrown when invoking the
         * super implementation of the proxied method i.e., the actual {@code @Bean} method
         */
        @Override
        public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
                MethodProxy cglibMethodProxy) throws Throwable {

            //ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
            //String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

            return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);

        }

        /**
         * Check the BeanFactory to see whether the bean named <var>beanName</var> already
         * exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
         * we're in the middle of servicing the initial request for this bean. From an enhanced
         * factory method's perspective, this means that the bean does not actually yet exist,
         * and that it is now our job to create it for the first time by executing the logic
         * in the corresponding factory method.
         * <p>Said another way, this check repurposes
         * {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether
         * the container is calling this method or the user is calling this method.
         * @param beanName name of bean to check for
         * @return whether <var>beanName</var> already exists in the factory
         */
        private boolean factoryContainsBean(ConfigurableBeanFactory beanFactory, String beanName) {
            return (beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName));
        }

        /**
         * Check whether the given method corresponds to the container's currently invoked
         * factory method. Compares method name and parameter types only in order to work
         * around a potential problem with covariant return types (currently only known
         * to happen on Groovy classes).
         */
        private boolean isCurrentlyInvokedFactoryMethod(Method method) {
            Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
            return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName())
                    && Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
        }

        /**
         * Create a subclass proxy that intercepts calls to getObject(), delegating to the current BeanFactory
         * instead of creating a new instance. These proxies are created only when calling a FactoryBean from
         * within a Bean method, allowing for proper scoping semantics even when working against the FactoryBean
         * instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
         * it will not be proxied. This too is aligned with the way XML configuration works.
         */
        private Object enhanceFactoryBean(final Object factoryBean, final ConfigurableBeanFactory beanFactory,
                final String beanName) {

            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(factoryBean.getClass());
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setCallbackType(MethodInterceptor.class);

            // Ideally create enhanced FactoryBean proxy without constructor side effects,
            // analogous to AOP proxy creation in ObjenesisCglibAopProxy...
            Class<?> fbClass = enhancer.createClass();
            Object fbProxy = null;

            if (objenesis.isWorthTrying()) {
                try {
                    fbProxy = objenesis.newInstance(fbClass, enhancer.getUseCache());
                } catch (ObjenesisException ex) {
                    logger.debug("Unable to instantiate enhanced FactoryBean using Objenesis, "
                            + "falling back to regular construction", ex);
                }
            }

            if (fbProxy == null) {
                try {
                    fbProxy = fbClass.newInstance();
                } catch (Exception ex) {
                    throw new IllegalStateException(
                            "Unable to instantiate enhanced FactoryBean using Objenesis, "
                                    + "and regular FactoryBean instantiation via default constructor fails as well",
                            ex);
                }
            }

            ((Factory) fbProxy).setCallback(0, new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                        throws Throwable {
                    if (method.getName().equals("getObject") && args.length == 0) {
                        return beanFactory.getBean(beanName);
                    }
                    return proxy.invoke(factoryBean, args);
                }
            });

            return fbProxy;
        }

        private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {
            Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
            Assert.state(field != null, "Unable to find generated bean factory field");
            Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
            Assert.state(beanFactory != null, "BeanFactory has not been injected into @Configuration class");
            Assert.state(beanFactory instanceof ConfigurableBeanFactory,
                    "Injected BeanFactory is not a ConfigurableBeanFactory");
            return (ConfigurableBeanFactory) beanFactory;
        }

        @Override
        public boolean isMatch(Method candidateMethod) {
            return true;
        }
    }

}