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