All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.fashionbrot.value.AbstractAnnotationInjectedBeanPostProcessor Maven / Gradle / Ivy

package com.github.fashionbrot.value;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static com.github.fashionbrot.util.MarsUtil.resolveGenericType;
import static org.springframework.core.BridgeMethodResolver.findBridgedMethod;
import static org.springframework.core.BridgeMethodResolver.isVisibilityBridgeMethodPair;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
import static org.springframework.core.annotation.AnnotationUtils.getAnnotation;

/**
 * @author fashionbrot
 * @date 2021/07/28 22:45
 *
 * Abstract generic {@link BeanPostProcessor} implementation for customized annotation that annotated injected-object.
 *
 * @param  The type of {@link Annotation customized annotation}
 */
public abstract class AbstractAnnotationInjectedBeanPostProcessor extends
        InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered,
        BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean {

    private final static int CACHE_SIZE = Integer.getInteger("", 32);

    private final Log logger = LogFactory.getLog(getClass());

    private final Class annotationType;

    private final ConcurrentMap injectionMetadataCache = new ConcurrentHashMap<>(CACHE_SIZE);

    private final ConcurrentMap injectedObjectsCache = new ConcurrentHashMap<>(CACHE_SIZE);

    private ConfigurableListableBeanFactory beanFactory;

    private Environment environment;

    private ClassLoader classLoader;

    private int order = Ordered.LOWEST_PRECEDENCE;

    protected AbstractAnnotationInjectedBeanPostProcessor() {
        this.annotationType = resolveGenericType(getClass());
    }

    private static  Collection combine(Collection... elements) {
        List allElements = new ArrayList();
        for (Collection e : elements) {
            allElements.addAll(e);
        }
        return allElements;
    }

    /**
     * Annotation type
     *
     * @return non-null
     */
    private final Class getAnnotationType() {
        return annotationType;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory,
                "AnnotationInjectedBeanPostProcessor requires a ConfigurableListableBeanFactory");
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    @Override
    public PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {

        InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
        try {
            metadata.inject(bean, beanName, pvs);
        } catch (BeanCreationException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getName()
                    + " dependencies is failed", ex);
        }
        return pvs;
    }


    /**
     * Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated {@link A} fields
     *
     * @param beanClass The {@link Class} of Bean
     * @return non-null {@link List}
     */
    private List findFieldAnnotationMetadata(final Class beanClass) {

        final List elements = new LinkedList<>();

        ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

                A annotation = getAnnotation(field, getAnnotationType());

                if (annotation != null) {

                    if (Modifier.isStatic(field.getModifiers())) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("@" + getAnnotationType().getName() + " is not supported on static fields: " + field);
                        }
                        return;
                    }

                    elements.add(new AnnotatedFieldElement(field, annotation));
                }
            }

        });

        return elements;

    }

    /**
     * Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated {@link A @A} methods
     *
     * @param beanClass The {@link Class} of Bean
     * @return non-null {@link List}
     */
    private List findAnnotatedMethodMetadata(final Class beanClass) {

        final List elements = new LinkedList<>();

        ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {

            @Override
            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                
                Method bridgedMethod = findBridgedMethod(method);

                if (!isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                    return;
                }

                A annotation = findAnnotation(bridgedMethod, getAnnotationType());

                if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
                    if (Modifier.isStatic(method.getModifiers())) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("@" + getAnnotationType().getSimpleName() + " annotation is not supported on static methods: " + method);
                        }
                        return;
                    }
                    if (method.getParameterTypes().length == 0) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("@" + getAnnotationType().getSimpleName() + " annotation should only be used on methods with parameters: " +
                                    method);
                        }
                    }
                    PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
                    elements.add(new AnnotatedMethodElement(method, pd, annotation));
                }

            }
        });

        return elements;

    }


    private AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class beanClass) {
        Collection fieldElements = findFieldAnnotationMetadata(beanClass);
        Collection methodElements = findAnnotatedMethodMetadata(beanClass);
        return new AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);

    }

    private InjectionMetadata findInjectionMetadata(String beanName, Class clazz, PropertyValues pvs) {
        // Fall back to class name as cache key, for backwards compatibility with custom callers.
        String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
        // Quick check on the concurrent map first, with minimal locking.
        AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
        if (InjectionMetadata.needsRefresh(metadata, clazz)) {
            synchronized (this.injectionMetadataCache) {
                metadata = this.injectionMetadataCache.get(cacheKey);
                if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                    if (metadata != null) {
                        metadata.clear(pvs);
                    }
                    try {
                        metadata = buildAnnotatedMetadata(clazz);
                        this.injectionMetadataCache.put(cacheKey, metadata);
                    } catch (NoClassDefFoundError err) {
                        throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() +
                                "] for annotation metadata: could not find class that it depends on", err);
                    }
                }
            }
        }
        return metadata;
    }

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) {
        if (beanType != null) {
            InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
            metadata.checkConfigMembers(beanDefinition);
        }
    }

    @Override
    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Override
    public void destroy() throws Exception {
        for (Object object : injectedObjectsCache.values()) {
            if (logger.isInfoEnabled()) {
                logger.info(object + " was destroying!");
            }

            if (object instanceof DisposableBean) {
                ((DisposableBean) object).destroy();
            }
        }

        injectionMetadataCache.clear();
        injectedObjectsCache.clear();

        if (logger.isInfoEnabled()) {
            logger.info(getClass() + " was destroying!");
        }

    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    private Environment getEnvironment() {
        return environment;
    }

    private ClassLoader getClassLoader() {
        return classLoader;
    }

    protected ConfigurableListableBeanFactory getBeanFactory() {
        return beanFactory;
    }

    /**
     * Gets all injected-objects.
     *
     * @return non-null {@link Collection}
     */
    protected Collection getInjectedObjects() {
        return this.injectedObjectsCache.values();
    }

    /**
     * Get injected-object from specified {@link A annotation} and Bean Class
     *
     * @param annotation      {@link A annotation}
     * @param bean            Current bean that will be injected
     * @param beanName        Current bean name that will be injected
     * @param injectedType    the type of injected-object
     * @param injectedElement {@link InjectionMetadata.InjectedElement}
     * @return An injected object
     * @throws Exception If getting is failed
     */
    protected Object getInjectedObject(A annotation, Object bean, String beanName, Class injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {

        String cacheKey = buildInjectedObjectCacheKey(annotation, bean, beanName, injectedType, injectedElement);

        Object injectedObject = injectedObjectsCache.get(cacheKey);

        if (injectedObject == null) {
            injectedObject = doGetInjectedBean(annotation, bean, beanName, injectedType, injectedElement);
            // Customized inject-object if necessary
            injectedObjectsCache.putIfAbsent(cacheKey, injectedObject);
        }

        return injectedObject;

    }

    /**
     * Subclass must implement this method to get injected-object. The context objects could help this method if
     * necessary :
     * 
    *
  • {@link #getBeanFactory() BeanFactory}
  • *
  • {@link #getClassLoader() ClassLoader}
  • *
  • {@link #getEnvironment() Environment}
  • *
* * @param annotation {@link A annotation} * @param bean Current bean that will be injected * @param beanName Current bean name that will be injected * @param injectedType the type of injected-object * @param injectedElement {@link InjectionMetadata.InjectedElement} * @return The injected object * @throws Exception If resolving an injected object is failed. */ protected abstract Object doGetInjectedBean(A annotation, Object bean, String beanName, Class injectedType, InjectionMetadata.InjectedElement injectedElement) throws Exception; /** * Build a cache key for injected-object. The context objects could help this method if * necessary : *
    *
  • {@link #getBeanFactory() BeanFactory}
  • *
  • {@link #getClassLoader() ClassLoader}
  • *
  • {@link #getEnvironment() Environment}
  • *
* * @param annotation {@link A annotation} * @param bean Current bean that will be injected * @param beanName Current bean name that will be injected * @param injectedType the type of injected-object * @param injectedElement {@link InjectionMetadata.InjectedElement} * @return Bean cache key */ protected abstract String buildInjectedObjectCacheKey(A annotation, Object bean, String beanName, Class injectedType, InjectionMetadata.InjectedElement injectedElement); /** * Get {@link Map} in injected field. * * @return non-null ready-only {@link Map} */ protected Map getInjectedFieldObjectsMap() { Map injectedElementBeanMap = new LinkedHashMap(); for (AnnotatedInjectionMetadata metadata : injectionMetadataCache.values()) { Collection fieldElements = metadata.getFieldElements(); for (AnnotatedFieldElement fieldElement : fieldElements) { injectedElementBeanMap.put(fieldElement, fieldElement.bean); } } return Collections.unmodifiableMap(injectedElementBeanMap); } /** * Get {@link Map} in injected method. * * @return non-null {@link Map} */ protected Map getInjectedMethodObjectsMap() { Map injectedElementBeanMap = new LinkedHashMap(); for (AnnotatedInjectionMetadata metadata : injectionMetadataCache.values()) { Collection methodElements = metadata.getMethodElements(); for (AnnotatedMethodElement methodElement : methodElements) { injectedElementBeanMap.put(methodElement, methodElement.object); } } return Collections.unmodifiableMap(injectedElementBeanMap); } /** * {@link A} {@link InjectionMetadata} implementation */ private class AnnotatedInjectionMetadata extends InjectionMetadata { private final Collection fieldElements; private final Collection methodElements; public AnnotatedInjectionMetadata(Class targetClass, Collection fieldElements, Collection methodElements) { super(targetClass, combine(fieldElements, methodElements)); this.fieldElements = fieldElements; this.methodElements = methodElements; } public Collection getFieldElements() { return fieldElements; } public Collection getMethodElements() { return methodElements; } } /** * {@link A} {@link Method} {@link InjectionMetadata.InjectedElement} */ private class AnnotatedMethodElement extends InjectionMetadata.InjectedElement { private final Method method; private final A annotation; private volatile Object object; protected AnnotatedMethodElement(Method method, PropertyDescriptor pd, A annotation) { super(method, pd); this.method = method; this.annotation = annotation; } @Override protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Class injectedType = pd.getPropertyType(); Object injectedObject = getInjectedObject(annotation, bean, beanName, injectedType, this); ReflectionUtils.makeAccessible(method); method.invoke(bean, injectedObject); } } /** * {@link A} {@link Field} {@link InjectionMetadata.InjectedElement} */ public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement { private final Field field; private final A annotation; private volatile Object bean; protected AnnotatedFieldElement(Field field, A annotation) { super(field, null); this.field = field; this.annotation = annotation; } @Override protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Class injectedType = field.getType(); Object injectedObject = getInjectedObject(annotation, bean, beanName, injectedType, this); ReflectionUtils.makeAccessible(field); field.set(bean, injectedObject); } } }