Package com.sishuok.spring.dynamic

Source Code of com.sishuok.spring.dynamic.DynamicDeployBeans

package com.sishuok.spring.dynamic;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.scripting.groovy.GroovyScriptFactory;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.util.*;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
* <p>User: Zhang Kaitao
* <p>Date: 14-1-3
* <p>Version: 1.0
*/
public class DynamicDeployBeans {

    protected static final Log logger = LogFactory.getLog(DynamicDeployBeans.class);

    //RequestMappingHandlerMapping
    private static Method detectHandlerMethodsMethod =
            ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "detectHandlerMethods", Object.class);
    private static Method getMappingForMethodMethod =
            ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingForMethod", Method.class, Class.class);
    private static Method getMappingPathPatternsMethod =
            ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingPathPatterns", RequestMappingInfo.class);
    private static Method getPathMatcherMethod =
            ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getPathMatcher");
    private static Field handlerMethodsField =
            ReflectionUtils.findField(RequestMappingHandlerMapping.class, "handlerMethods", Map.class);
    private static Field urlMapField =
            ReflectionUtils.findField(RequestMappingHandlerMapping.class, "urlMap", MultiValueMap.class);

    private static Field injectionMetadataCacheField =
            ReflectionUtils.findField(AutowiredAnnotationBeanPostProcessor.class, "injectionMetadataCache");

    static {
        detectHandlerMethodsMethod.setAccessible(true);
        getMappingForMethodMethod.setAccessible(true);
        getMappingPathPatternsMethod.setAccessible(true);
        getPathMatcherMethod.setAccessible(true);
        handlerMethodsField.setAccessible(true);
        urlMapField.setAccessible(true);

        injectionMetadataCacheField.setAccessible(true);
    }

    private ApplicationContext ctx;
    private DefaultListableBeanFactory beanFactory;

    private Map<String, Long> scriptLastModifiedMap = new ConcurrentHashMap<>();//in millis

    public DynamicDeployBeans() {
        this(-1L);
    }

    public DynamicDeployBeans(Long scriptCheckInterval) {
        if (scriptCheckInterval > 0L) {
            startScriptModifiedCheckThead(scriptCheckInterval);
        }
    }


    @Autowired
    public void setApplicationContext(ApplicationContext ctx) {
        if (!DefaultListableBeanFactory.class.isAssignableFrom(ctx.getAutowireCapableBeanFactory().getClass())) {
            throw new IllegalArgumentException("BeanFactory must be DefaultListableBeanFactory type");
        }
        this.ctx = ctx;
        this.beanFactory = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
    }

    public void registerBean(Class<?> beanClass) {
        registerBean(null, beanClass);
    }

    public void registerBean(String beanName, Class<?> beanClass) {
        Assert.notNull(beanClass, "register bean class must not null");
        GenericBeanDefinition bd = new GenericBeanDefinition();
        bd.setBeanClass(beanClass);

        if (StringUtils.hasText(beanName)) {
            beanFactory.registerBeanDefinition(beanName, bd);
        } else {
            BeanDefinitionReaderUtils.registerWithGeneratedName(bd, beanFactory);
        }

    }

    public void registerController(Class<?> controllerClass) {
        Assert.notNull(controllerClass, "register controller bean class must not null");
        if (!WebApplicationContext.class.isAssignableFrom(ctx.getClass())) {
            throw new IllegalArgumentException("applicationContext must be WebApplicationContext type");
        }

        GenericBeanDefinition bd = new GenericBeanDefinition();
        bd.setBeanClass(controllerClass);

        String controllerBeanName = controllerClass.getName();
        removeOldControllerMapping(controllerBeanName);
        beanFactory.registerBeanDefinition(controllerBeanName, bd);
        addControllerMapping(controllerBeanName);
    }


    public void registerGroovyController(String scriptLocation) throws IOException {

        if (scriptNotExists(scriptLocation)) {
            throw new IllegalArgumentException("script not exists : " + scriptLocation);
        }
        scriptLastModifiedMap.put(scriptLocation, scriptLastModified(scriptLocation));

        // Create script factory bean definition.
        GroovyScriptFactory groovyScriptFactory = new GroovyScriptFactory(scriptLocation);
        groovyScriptFactory.setBeanFactory(beanFactory);
        groovyScriptFactory.setBeanClassLoader(beanFactory.getBeanClassLoader());
        Object controller =
                groovyScriptFactory.getScriptedObject(new ResourceScriptSource(ctx.getResource(scriptLocation)));

        String controllerBeanName = scriptLocation;
        removeOldControllerMapping(controllerBeanName);
        if (beanFactory.containsBean(controllerBeanName)) {
            beanFactory.destroySingleton(controllerBeanName); //移除单例bean
            removeInjectCache(controller); //移除注入缓存 否则Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
        }
        beanFactory.registerSingleton(controllerBeanName, controller); //注册单例bean
        beanFactory.autowireBean(controller); //自动注入
        addControllerMapping(controllerBeanName);
    }

    private void removeOldControllerMapping(String controllerBeanName) {

        if (!beanFactory.containsBean(controllerBeanName)) {
            return;
        }
        RequestMappingHandlerMapping requestMappingHandlerMapping = requestMappingHandlerMapping();


        //remove old
        Class<?> handlerType = ctx.getType(controllerBeanName);
        final Class<?> userType = ClassUtils.getUserClass(handlerType);

        Map handlerMethods = (Map) ReflectionUtils.getField(handlerMethodsField, requestMappingHandlerMapping);
        MultiValueMap urlMapping = (MultiValueMap) ReflectionUtils.getField(urlMapField, requestMappingHandlerMapping);

        final RequestMappingHandlerMapping innerRequestMappingHandlerMapping = requestMappingHandlerMapping;
        Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new ReflectionUtils.MethodFilter() {
            @Override
            public boolean matches(Method method) {
                return ReflectionUtils.invokeMethod(
                        getMappingForMethodMethod,
                        innerRequestMappingHandlerMapping,
                        method, userType) != null;
            }
        });

        for (Method method : methods) {
            RequestMappingInfo mapping =
                    (RequestMappingInfo) ReflectionUtils.invokeMethod(getMappingForMethodMethod, requestMappingHandlerMapping, method, userType);

            handlerMethods.remove(mapping);

            Set<String> patterns =
                    (Set<String>) ReflectionUtils.invokeMethod(getMappingPathPatternsMethod, requestMappingHandlerMapping, mapping);

            PathMatcher pathMatcher =
                    (PathMatcher) ReflectionUtils.invokeMethod(getPathMatcherMethod, requestMappingHandlerMapping);

            for (String pattern : patterns) {
                if (!pathMatcher.isPattern(pattern)) {
                    urlMapping.remove(pattern);
                }
            }
        }

    }


    private void addControllerMapping(String controllerBeanName) {

        removeOldControllerMapping(controllerBeanName);

        RequestMappingHandlerMapping requestMappingHandlerMapping = requestMappingHandlerMapping();
        //spring 3.1 开始
        ReflectionUtils.invokeMethod(detectHandlerMethodsMethod, requestMappingHandlerMapping, controllerBeanName);
    }


    private RequestMappingHandlerMapping requestMappingHandlerMapping() {
        try {
            return ctx.getBean(RequestMappingHandlerMapping.class);
        } catch (Exception e) {
            throw new IllegalArgumentException("applicationContext must has RequestMappingHandlerMapping");
        }
    }


    private void removeInjectCache(Object controller) {

        AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor =
                ctx.getBean(AutowiredAnnotationBeanPostProcessor.class);

        Map<String, InjectionMetadata> injectionMetadataMap =
                (Map<String, InjectionMetadata>) ReflectionUtils.getField(injectionMetadataCacheField, autowiredAnnotationBeanPostProcessor);

        injectionMetadataMap.remove(controller.getClass().getName());
    }


    private void startScriptModifiedCheckThead(final Long scriptCheckInterval) {
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {

                        Thread.sleep(scriptCheckInterval);

                        Map<String, Long> copyMap = new HashMap<>(scriptLastModifiedMap);
                        for (String scriptLocation : copyMap.keySet()) {

                            if (scriptNotExists(scriptLocation)) {
                                scriptLastModifiedMap.remove(scriptLocation);
                                //TODO remove handler mapping ?
                            }
                            if (copyMap.get(scriptLocation) != scriptLastModified(scriptLocation)) {
                                registerGroovyController(scriptLocation);
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        //ignore
                    }
                }
            }
        }.start();
    }


    private long scriptLastModified(String scriptLocation) {
        try {
            return ctx.getResource(scriptLocation).getFile().lastModified();
        } catch (Exception e) {
            return -1;
        }
    }

    private boolean scriptNotExists(String scriptLocation) {
        return !ctx.getResource(scriptLocation).exists();
    }

}
TOP

Related Classes of com.sishuok.spring.dynamic.DynamicDeployBeans

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.