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

org.zodiac.autoconfigure.proxy.SmartProxyAutoConfiguration Maven / Gradle / Ivy

There is a newer version: 1.6.8
Show newest version
package org.zodiac.autoconfigure.proxy;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.slf4j.Logger;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.util.ClassUtils;
import org.zodiac.autoconfigure.application.main.ApplicationMainProperties;
import org.zodiac.commons.constants.SystemPropertiesConstants;
import org.zodiac.commons.logging.SmartSlf4jLoggerFactory;
import org.zodiac.commons.proxy.DispatcherProxyInvocation;
import org.zodiac.commons.proxy.InvocationChainFilter;
import org.zodiac.commons.util.ArrayUtil;
import org.zodiac.commons.util.Asserts;
import org.zodiac.commons.util.Classes;
import org.zodiac.commons.util.Colls;
import org.zodiac.commons.util.Reflections;
import org.zodiac.commons.util.lang.Strings;
import org.zodiac.core.proxy.DispatcherFactoryBeanSmartProxyInvocation;
import org.zodiac.core.proxy.SmartFactoryBeanProxy;
import org.zodiac.core.proxy.SmartProxy;
import org.zodiac.core.proxy.SmartProxyConfigurator;
import org.zodiac.core.proxy.SmartProxyFilter;
import org.zodiac.core.proxy.SmartProxyFor;
import org.zodiac.core.proxy.SmartProxyIgnored;

/**
 * Intelligent AOP enhanced proxy configurator supports proxy creation of
 * various types of beans, such as: Simple Object, {@link FactoryBean} and
 * {@link org.mybatis.spring.mapper.MapperFactoryBean} etc.
 * 
 */
@SpringBootConfiguration
@Order(value = Ordered.LOWEST_PRECEDENCE - 1)
@ConditionalOnProperty(value = SystemPropertiesConstants.Zodiac.SPRING_MAIN_SMART_PROXY_ENABLED, havingValue = "true", matchIfMissing = true)
public class SmartProxyAutoConfiguration implements InitializingBean, BeanPostProcessor, SmartProxyConfigurator {

    /**
     * Excludes bean class base packages.
     */
    public static final String[] EXCLUDE_BASE_PACKAGES = { "org.springframework", "java.", "javax." };

    // JDK proxy interface field.
    public static final Field PROXY_INTERFACE_FIELD = Reflections.findFieldNullable(Proxy.class, "h", InvocationHandler.class);
    // Mybatis mapper proxy classes and methods and fields.
    public static final Class MAPPER_FACTORY_BEAN_CLASS = Classes.resolveClassNameNullable("org.mybatis.spring.mapper.MapperFactoryBean");
    public static final Field MAPPER_FACTORY_BEAN_INTERFACE_FIELD = Reflections.findFieldNullable(MAPPER_FACTORY_BEAN_CLASS, "mapperInterface",
            Class.class);
    public static final Class MAPPER_PROXY_CLASS = Classes.resolveClassNameNullable("org.apache.ibatis.binding.MapperProxy");
    public static final Field MAPPER_PROXY_INTERFACE_FIELD = Reflections.findFieldNullable(MAPPER_PROXY_CLASS, "mapperInterface", Class.class);

    protected final Logger log = SmartSlf4jLoggerFactory.getLogger(getClass());

    private final Map, List> knownProxiedMapping = Colls.concurrentMap(4);

//    @Value("${" + KEY_SMART_PROXY + ".base-packages:}")
    private String[] basePackages;
    //@Autowired
    private DefaultListableBeanFactory beanFactory;
    //@Autowired(required = false)
    private List processors;

    public SmartProxyAutoConfiguration(ApplicationMainProperties applicationMainProperties,
        DefaultListableBeanFactory beanFactory, ObjectProvider> processorsProvider) {
        this.basePackages = applicationMainProperties.getSmartProxy().getBasePackages();
        this.beanFactory = beanFactory;
        this.processors = processorsProvider.getIfAvailable();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.processors = Colls.safeList(processors);
        AnnotationAwareOrderComparator.sort(processors);
    }

    /**
     * {@link AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization(Object, String)}
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        /*Gets actual original target class.*/
        Class targetClass = getOriginalActualTargetClass(bean, beanName);

        if (skipNoShouldProxy(bean, beanName, targetClass)) {
            return bean;
        }

        if (Objects.isNull(targetClass)) {
            /*Unable proxy*/
            log.warn("Skip bean that cannot be proxied, because cannot get actual original target class. - '{}'", bean);
            return bean;
        }

        /*Skip exclude beans*/
        if (skipExcludeBean(bean, beanName, targetClass)) {
            return bean;
        }

        /*Add known proxied mappings.*/
        boolean hasSupportProxied = false;
        for (SmartProxyFilter p : processors) {
            if (p.supportTypeProxy(bean, targetClass)) {
                hasSupportProxied = true;
                /*Add proxies handler mapping.*/
                addKnownProxiedMapping(targetClass, p);
            }
        }

        if (hasSupportProxied) {
            /*Wrap to proxy bean.*/
            return wrapEnhanceProxyBean(bean, beanName, targetClass);
        }
        return bean;
    }

    /**
     * Wrap object bean to enhanced proxy bean.
     * 
     * @param bean bean object
     * @param beanName bean name
     * @param targetClass target class
     * @return proxyed object
     */
    private Object wrapEnhanceProxyBean(Object bean, String beanName, final Class targetClass) {
        Enhancer enhancer = new Enhancer() {
            @Override
            protected void setNamePrefix(String namePrefix) {
                Class proxyClass = (bean instanceof FactoryBean) ? SmartFactoryBeanProxy.class : SmartProxy.class;
                super.setNamePrefix(targetClass.getName().concat(Classes.CGLIB_CLASS_SEPARATOR).concat(proxyClass.getSimpleName()));
            }
        };
        /*Sets enhanced interfaces or superClass.*/
        List> enhancedInterfaces = new ArrayList<>(4);
        if (targetClass.isInterface()) { // Is interface
            enhancedInterfaces.add(targetClass);
        } else {
            /*Is class*/
            /*[START] extension logic.(In order to enhance the class modified by final)*/
            SmartProxyFor proxyFor = AnnotationUtils.findAnnotation(targetClass, SmartProxyFor.class);
            if (Objects.nonNull(proxyFor)) {
                /*Priority is given to display defined enhanced interfaces.*/
                Class[] interfaces = proxyFor.interfaces();
                if (ArrayUtil.emptyArray(interfaces)) {
                    /*Fallback, using all interfaces of the targetClass.*/
                    interfaces = targetClass.getInterfaces();
                }
                enhancedInterfaces.addAll(Arrays.asList(interfaces));
                /*[END] extension logic.*/
            } else {
                /*No interfaces*/
                Asserts.isTrue(!Modifier.isFinal(targetClass.getModifiers()),
                        () -> String.format("Enhance proxy target class must a interface or not final type. - %s", targetClass));
                enhancer.setSuperclass(targetClass);
            }
        }
        if (bean instanceof FactoryBean) {
            enhancedInterfaces.add(SmartFactoryBeanProxy.class);
            enhancer.setCallback(new DispatcherFactoryBeanSmartProxyInvocation(beanFactory, this, bean, beanName, targetClass));
        } else {
            enhancedInterfaces.add(SmartProxy.class);
            enhancer.setCallback(new DispatcherProxyInvocation(beanFactory, this, beanName, targetClass, () -> bean));
        }
        enhancer.setInterfaces(enhancedInterfaces.toArray(new Class[0]));

        final Object proxy = enhancer.create();
        log.info("Created smart proxy: '{}' of actual original target class: '{}'", proxy, targetClass);
        return proxy;
    }

    /**
     * Addidition known proxied and {@link SmartProxyFilter} mappings.
     * 
     * @param targetClass target class
     * @param processor filter
     */
    private void addKnownProxiedMapping(Class targetClass, SmartProxyFilter processor) {
        List processors = knownProxiedMapping.get(targetClass);
        if (Objects.isNull(processors)) {
            processors = new ArrayList<>(4);
        }
        if (!processors.contains(processor)) {
            processors.add(processor);
        }
        knownProxiedMapping.put(targetClass, processors);
    }

    @Override
    public final List getProcessors(Class targetClass) {
        return Colls.safeList(knownProxiedMapping.get(targetClass));
    }

    /**
     * Whether kip not should proxy bean.
     * 
     * @param bean bean object
     * @param beanName bean name
     * @param targetClass type of target object being proxied
     * @return skipped or not
     */
    protected boolean skipNoShouldProxy(Object bean, String beanName, final Class targetClass) {
        if (null == bean || Strings.blank(beanName) || null == targetClass) {
            return false;
        }
        SmartProxyIgnored proxyIgnored = AnnotationUtils.findAnnotation(targetClass, SmartProxyIgnored.class);
        if (null != proxyIgnored) {
            /*Ignored class*/
            return true;
        }

        if (Reflections.hasDeaultConstructor(targetClass)) {
            /*
            No default constructor, is it against for CGLIB.
            In this case, the proxy throws an exception {@code java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given}.
            Refer to Superclass has no null constructors but no arguments were given
            */
            return true;
        }

        if (Strings.startsWithAny(bean.getClass().getName(), EXCLUDE_BASE_PACKAGES)) {
            /*Ignore spring internal bean?*/
            return true;
        } else if (Classes.anyTypeOf(bean, SmartFactoryBeanProxy.class, SmartProxy.class)) {
            /*Proxied bean? (Only SmartProxy proxy is allowed once)*/
            return true;
        }
        return false;
    }

    /**
     * Skip exclude beans by base packages.
     * 
     * @param bean bean object
     * @param beanName bean name
     * @param targetClass target class
     * @return skipped or not
     */
    protected boolean skipExcludeBean(Object bean, String beanName, Class targetClass) {
        return !(ArrayUtil.emptyArray(basePackages) || Strings.startsWithAny(Classes.getPackageName(targetClass), basePackages));
    }

    /**
     * Get the actual type of the bean according to the default policy. If the
     * bean has been proxied, it represents the interface or class of the actual
     * proxy.
     * 
     * @param bean bean object
     * @param beanName bean name
     * @return target class
     */
    @SuppressWarnings("rawtypes")
    protected Class getOriginalActualTargetClass(Object bean, String beanName) {
        if (bean.getClass().getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
            /*CGLIB-proxy-bean*/
            Class targetClass = bean.getClass().getSuperclass();
            if (Objects.nonNull(targetClass)) {
                return targetClass;
            }
            Class[] interfaces = bean.getClass().getInterfaces();
            return (Objects.nonNull(interfaces) && interfaces.length > 0) ? interfaces[0] : null;
        } else if (Proxy.class.isInstance(bean)) {
            /*JDK proxy bean*/
            return Reflections.getField(PROXY_INTERFACE_FIELD, bean, true).getClass();
        } else if (bean instanceof FactoryBean) {
            /*FactoryBean*/
            /*e.g: org.mybatis.spring.mapper.MapperFactoryBean#getObjectType()*/
            if (Objects.nonNull(MAPPER_FACTORY_BEAN_CLASS) && MAPPER_FACTORY_BEAN_CLASS.isInstance(bean)) {
                Object proxyInvocation = null;
                try {
                    proxyInvocation = ((FactoryBean) bean).getObject();
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                }
                if (Objects.nonNull(proxyInvocation) && Proxy.class.isInstance(proxyInvocation)) {
                    Object mapperProxy = Reflections.getField(PROXY_INTERFACE_FIELD, proxyInvocation, true);
                    /*e.g: org.apache.ibatis.binding.MapperProxy@6cb2ee5c ==> com.sun.proxy.$Proxy96*/
                    if (Objects.nonNull(MAPPER_PROXY_CLASS) && MAPPER_PROXY_CLASS.isInstance(mapperProxy)) {
                        return Reflections.getField(MAPPER_PROXY_INTERFACE_FIELD, mapperProxy, true);
                    }
                    return mapperProxy.getClass();
                }
            }
            return ((FactoryBean) bean).getObjectType();
        }
        return bean.getClass();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy