SpringBoot专题一:浅谈SpringBoot的自动装配机制及自定义starter


一、引言

SpringBoot是基于Spring基础上而生的一个微服务框架,相比于Spring,强调约定大于配置,具有自动化配置、快速开发,自动部署等优点。那么SpringBoot是怎么来简化原先Spring那些繁琐的配置的呢?自动装配。本篇博客,主要是想简单阐述一下SpringBoot的自动装配机制和手写自定义一个starter。

二、自动装配原理

2.1  从熟知的启动类开始

@SpringBootApplication
public class Start {

    public static void main(String[] args) {
// 启动IOC和tomcat容器 SpringApplication.run(Start.
class, args); } }

这个是我们熟悉的SpringBoot的启动类。自动装配的核心就是注解@SpringBootApplication。

2.2 注解@SpringBootApplication

@SpringBootApplication是SpringBoot自定义的一个注解。我们先来看这个注解的源码片段,实际上对自动装配产生作用的就是两个:

@SpringBootConfiguration 和 @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

先简单分析源码的前四个注解。这个四个注解属于Java的元注解,意为修饰注解的注解。

@Target:自定义注解的使用范围,如类、方法、属性。比方说@Controller是作用在类上的,如果作用在方法上就会报错。
@Retention:这个注解有三个属性:source、class、runtime(最常见也最常用),主要定义自定义注解的生效时间。
(1)source,意味着当Java文件编译成源文件(.class),这个注解就会被遗弃,只保留在源文件,主要是提供给编译用的,比方说我们熟悉的Override;
(2)class,意味着实现一些检查性的操作,如supperessWarning,摒弃一些告警标识;
(3)runtime:意味着运行时生效,最常见和使用。
@Documented:生成javadoc文档
@Inherited:所修饰的自定义注解可以被子类继承。如我们刚才看到的Start类,如果有一个类继承了它,那么默认也会拥有@SpringBootApplication注解的功能。

2.3  @SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
public @interface SpringBootConfiguration {

    /**
     * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
     * bean lifecycle behavior, e.g. to return shared singleton bean instances even in
     * case of direct {@code @Bean} method calls in user code. This feature requires
     * method interception, implemented through a runtime-generated CGLIB subclass which
     * comes with limitations such as the configuration class and its methods not being
     * allowed to declare {@code final}.
     * <p>
     * The default is {@code true}, allowing for 'inter-bean references' within the
     * configuration class as well as for external calls to this configuration's
     * {@code @Bean} methods, e.g. from another configuration class. If this is not needed
     * since each of this particular configuration's {@code @Bean} methods is
     * self-contained and designed as a plain factory method for container use, switch
     * this flag to {@code false} in order to avoid CGLIB subclass processing.
     * <p>
     * Turning off bean method interception effectively processes {@code @Bean} methods
     * individually like when declared on non-{@code @Configuration} classes, a.k.a.
     * "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally
     * equivalent to removing the {@code @Configuration} stereotype.
     * @return whether to proxy {@code @Bean} methods
     * @since 2.2
     */
    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;

}

这个注解只有一个属性proxyBeanMethods,而且这个属性是从Configuration继承而来的。默认为true,它会采用CGLIB去做代理这个配置类。

抛开上面三个元注解,就只有一个@Configuration。也就是说@SpringBootApplication可以等同于@Configuration注解。

扩展:@Configuration和@Component区别

2.4  @EnableAutoConfiguration

自动装配主要依赖@EnableAutoConfiguration来实现

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    Class<?>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    String[] excludeName() default {};

}

2.4.1 @Import注解解释

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /**
     * {@link Configuration @Configuration}, {@link ImportSelector},
     * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
     */
    Class<?>[] value();

}

由注解的解释,我们可以看到,@Import作为Spring的注解,主要是想将类注入到IOC中去,主要由以下三种用法:

(1)实现ImportSelector接口selectImport方法。这个方法返回的是类的全路径的数据,这个数组中的类都会被扫描到IOC容器。

public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}

(2)实现ImportBeanDefinitionRegistrar接口。注意看参数,BeanDefinitionRegistry registry,是通过registry将bean注入到IOC容器。这是一个手工注入bean,自己编码去实现。

public interface ImportBeanDefinitionRegistrar {

    /**
     * Register bean definitions as necessary based on the given annotation metadata of
     * the importing {@code @Configuration} class.
     * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
     * registered here, due to lifecycle constraints related to {@code @Configuration}
     * class processing.
     * <p>The default implementation delegates to
     * {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}.
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry current bean definition registry
     * @param importBeanNameGenerator the bean name generator strategy for imported beans:
     * {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a
     * user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator}
     * has been set. In the latter case, the passed-in strategy will be the same used for
     * component scanning in the containing application context (otherwise, the default
     * component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}).
     * @since 5.2
     * @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
     * @see ConfigurationClassPostProcessor#setBeanNameGenerator
     */
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
            BeanNameGenerator importBeanNameGenerator) {

        registerBeanDefinitions(importingClassMetadata, registry);
    }

    /**
     * Register bean definitions as necessary based on the given annotation metadata of
     * the importing {@code @Configuration} class.
     * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
     * registered here, due to lifecycle constraints related to {@code @Configuration}
     * class processing.
     * <p>The default implementation is empty.
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry current bean definition registry
     */
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }

}

(3)普通类,直接被Spring扫描到IOC中去。

2.4.2 @AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

我们来看:AutoConfigurationPackages.Registrar实现了ImportBeanDefinitionRegistrar就是想手动去注入bean。注册又是一个什么样的bean,又要做什么呢?

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            register(registry, new PackageImport(metadata).getPackageName());
        }

        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }

    }

我们看源码的registerBeanDefinitions方法,new PackageImport(),这是一个内部类,首先它会先去获取AutoConfigurationPackage,最终要获得这个类上的basePackages,当然如果你覆写了basePackage这个属性就是你定义,没有就是我们当前Start类的当前类的路径,这个包路径不允许程序所修改,得到这个路径之后,封装成一个不可变的集合赋给全局的变量this.packageNames。

private static final class PackageImports {

        private final List<String> packageNames;

        PackageImports(AnnotationMetadata metadata) {
            AnnotationAttributes attributes = AnnotationAttributes
                    .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
            List<String> packageNames = new ArrayList<>();
            for (String basePackage : attributes.getStringArray("basePackages")) {
                packageNames.add(basePackage);
            }
            for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
                packageNames.add(basePackageClass.getPackage().getName());
            }
            if (packageNames.isEmpty()) {
                packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
            }
            this.packageNames = Collections.unmodifiableList(packageNames);
        }

        List<String> getPackageNames() {
            return this.packageNames;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }
            return this.packageNames.equals(((PackageImports) obj).packageNames);
        }

        @Override
        public int hashCode() {
            return this.packageNames.hashCode();
        }

        @Override
        public String toString() {
            return "Package Imports " + this.packageNames;
        }

    }

 this.packageNames这个全局变量作用是什么呢?回到registerBeanDefinitions()实现的register,这里的packageNames就是刚才得到的包路径,这里当然还有我们熟悉和Spring生命周期相关的的BeanDefinition,通过BasePackage类构造器,赋值给全局packages,提供get()方法给别人做查询使用。

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        }
        else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }

2.4.3 @Import(AutoConfigurationImportSelector.class)

我们注解上的这个类:AutoConfigurationImportSelector

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

实现的这个方法返回包含类的全路径的数组。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

loadFactoryName主要是加载Bean的。getSpringFactoriesLoaderFactoryClass()返回地是EnableAutoConfiguration.class对象,getBeanClassLoader获得类加载器。这里使用到类加载器,主要就是想加载资源,加载什么样的资源,继续如下:

 
 
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

也就是最终会扫描:"META-INF/spring.factories"这个相对路径下的资源,我们找到这个资源:

这里面类似key-value的配置,value可以看作是一些具体的实现类,我们重点先关注下:org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,

这些配置的实现类最终都会加载到IOC容器中去。

 

 在这个EnableAutoConfiguration中,就有一个:org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration。这里实现中,通过@Bean就会把我们熟悉的DispacherServlet加载到IOC容器中去。这里就是为什么使用SpringBoot时,就没让我们再去配置DispacherServlet了。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {

    /*
     * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    @Configuration(proxyBeanMethods = false)
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    protected static class DispatcherServletConfiguration {

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
            return dispatcherServlet;
        }

当然,你可能问?光是加载到容器就足够了吗?因为以前可还需要将Servlet加载到tomcat容器的上下文啊,这样才能正常的运转啊?那么,这里SpringBoot是怎么做到的呢?

也是在同一个类,还有一个@Bean。看这个:DispatcherServletRegistrationBean。这个类的父类是:ServletRegistrationBean。可能你会比较熟悉,因为我们可能会利用它去开发和web三大组件相关的内容:Filter、Listener、Servlet。

@Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                    webMvcProperties.getServlet().getPath());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }

    }

ServletRegistrationBean继承于RegistrationBean。RegistrationBean这个类有几个是是实现类。其中,可以看到ServletListenerResgistrationBean(监听器)等。

 

 而在ServletRegistrationBean中,有这么一个方法:通过ServletContext的addServlet方法。将servlet添加到tomcat容器中去。这里的this.servlet是上面DispatcherServletRegistrationBean 通过构造器传入进来的dispacherServlet.

    @Override
    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
        String name = getServletName();
        return servletContext.addServlet(name, this.servlet);
    }

问题是:addRegistration这个是怎么触发的呢?再来看以下RegistrationBean。

public abstract class RegistrationBean implements ServletContextInitializer, Ordered 

这个类实现了一个接口:ServletContextInitializer,利用了tomcat的SPI规范,将这个接口的实现类注入到tomcat的上下文中去,在tomcat的上下文,就会去调用onStartup方法。最终就会触发到addRegistration。

@FunctionalInterface
public interface ServletContextInitializer {

    /**
     * Configure the given {@link ServletContext} with any servlets, filters, listeners
     * context-params and attributes necessary for initialization.
     * @param servletContext the {@code ServletContext} to initialize
     * @throws ServletException if any call against the given {@code ServletContext}
     * throws a {@code ServletException}
     */
    void onStartup(ServletContext servletContext) throws ServletException;

}

言归正传,再回到我们的AutoConfigurationImportSelector的getAutoConfigurationEntry,为了方便,这里再贴一下这个方法的代码:、

/**
     * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
     * of the importing {@link Configuration @Configuration} class.
     * @param annotationMetadata the annotation metadata of the configuration class
     * @return the auto-configurations that should be imported
     */
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

当扫描到一堆配置类之后,通过removeDuplicates方法做了一个去重,去重的目的就是因为在classpath下的spring.factories文件内容都要扫描,去除相同的配置。

getExclusions就是排除到定义的不需要自动装配的类。

checkExcludedClasses则是需要进行自动装配的类做一个检查,不合法的需要校验。

紧接着会加载一个过滤器再进行一遍过滤。这个过滤器就是AutoConfigurationImportFilter。

fireAutoConfigurationImportEvents相当于Springboot提供的一个扩展点,基于事件驱动的,如果程序想参与到这个自动装配的话,可以去监听这个事件,监听到这个事件之后,可以做自己的一些操作。相关AutoConfigurationImportListener

最后,将所有需要装配的类封装到一个对象上:AutoConfigurationEntry。最终转化作为selectImport的返回值。

三、自定义写一个starter

3.1 自动装配的使用场景

这个自动装配除了帮我们自动装配DispatcherServlet,帮我们去加载类似tomcat、redis等,那我们还可以用来做什么?比方说实际开发中的一些公共的jar包(公共组件)等;

举个例子,比方说我们去开发一个Filter,要把这个Filter加载到应用程序中去(RegistrationBean );或者想使用一个服务,@Autowired 去注入jar里面的一个bean,但是你直接注入是不行的,可能你需要先使用<bean/>标签或者@Bean的注解。因为我要把其他这个jar包的bean通过bean标签或者注解写入进来,就对我的代码有了侵入,因为假如这个jar包的名字改了,或者这么bean我不用了(就像刚才的filter),我除了剔除jar包,我还要修改很多依赖的代码。也就是这样公共包会显得比较重。

有了自动装配,比方说Filter,可以把filter的装配自闭在一个jar包里面,别人不想使用了,剔除相应jar就行。就像想使用@Autowired去注入别人bean,不需要自己写<bean/>或者@Bean。直接@Autowired去使用就好。

3.2 简单案例--自定义starter

先提及到一个开发规范,对于包命名问题。

  • autoconfiguration:自动装配的核心代码。
  • starter:管理Jar。如果是Spring官方的,spring-boot-starter-xxx, 如果是自己定义,命名xxx-spring-boot-starter。

3.2.1  使用spring.factories

首先,我们做一个简单获取当前时间的工具类和一个filter(filter就简单打印一个访问时间),放在util-spring-boot-autoconfigure

工程目录大概如下,boot-application依赖starter,starter依赖autoconfigure:

代码简单如下:

public class DateUtil {

    public String getNowTime() {
        LocalDateTime localDate = LocalDateTime.now();
        return localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS"));
    }

}
public class MyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        long start = System.currentTimeMillis();
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        filterChain.doFilter(servletRequest, servletResponse);
        long end = System.currentTimeMillis();
        System.out.println(request.getRequestURI() + "执行时间:" + (end - start));
    }
}

自定义DateConfig,把前面这两个加载进来:

@Configuration
public class DateConfig {

    @Bean
    public DateUtil getDateUtil() {
        return new DateUtil();
    }

    @Bean
    public FilterRegistrationBean registerFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new MyFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setName("costFilter");
        filterRegistrationBean.setOrder(1);
        return filterRegistrationBean;
    }

}

最后在spring.factories中写入:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=util.spring.boot.autoconfigure.DateConfig

3.2.2 使用注解的方式

核心通过实现ImportSelector:

public class MyImport implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{DateConfig.class.getName()};
    }
}

自定义注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImport.class)
public @interface EnableUtil {
}

这个时候,可以不需要再spring.factories中配置,但是需要在启动类上增加修饰注解:

@SpringBootApplication
@EnableUtil
public class Start {
    public static void main(String[] args) {
        SpringApplication.run(Start.class, args);
    }
}

3.2.3 直接Import

@SpringBootApplication
@Import(DateConfig.class)
public class Start {
    public static void main(String[] args) {
        SpringApplication.run(Start.class, args);
    }
}

对比上面三种方式,第一种最好,因为实现了零侵入,可插拔。

 

本文涉及的demo测试代码可以参考gitee: https://gitee.com/leijisong/my-boot-starter

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM