SpringBoot之Configuration注解和EnableAutoConfiguration

背景

最近在做运营端的国际化,需要接入国际化拦截器(i18n拦截器),该拦截器用于向Cookie写入中文或其他语言的(zh_CN,en_US),当然该拦截器还要很多功能,没时间看源码,不是很了解。
该拦截器可以通过url参数将cookie信息写入为需要切换的语言,也可直接修改cookie值。
为了保证灰度,它会从配置中心获取Erp列表名称,如果在里头才会切换cookie语言。

问题(踩过的坑)

  • 如果你希望在SpringBoot中配置拦截器调用顺序
  • 如果你发现你的拦截器总是先执行,maven引入的拦截器总是后执行(配置依赖顺序)
  • 如果你想了解一些springboot相关注解

直接看总结也可以方便查看你所需要了解的。

我是这样配置该拦截器的,将Interceptor加入了拦截器链中,需要用到的XML也都引入了,可是我发送请求http://www.xxx.com?language=en_US时发现cookie写入不成功。

/**
 * 接入国际化登陆拦截器
 */
@Configuration
public class WebMvcI18nConfiguration extends WebMvcConfigurerAdapter {
    @Resource
    SpringUnionI18nInterceptor manDefaultSpringUnionI18nInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(manDefaultSpringUnionI18nInterceptor).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

排查

我这边发现每次都是manDefaultSpringUnionI18nInterceptor该国际化拦截器先执行,springSSOInterceptor该erp拦截器后执行。
先走erp拦截器,把需要用到的东西放到loginContext上下文中,然后再走i18n拦截器(通过灰度名单来控制是否展示国际化页面,loginContext.getPin()来获取erp名,如果先i18n拦截器,那么永远不能让国际化拦截器将cookie的语言key-value值更改。

我们这边重新实现了一个类似于spring-boot-starter的jar包,通过MAVEN方式导入。它会将spring-boot-autoconfigure包下载下来,有哪些东西呢,往下看:
先看下将ERP拦截器如何加载进拦截器链的,跟我配的一样。(具体实现忽视即可:删掉了一些创建bean的方法,并将一些单词改为xxx了)

// erp拦截器
@Configuration
@ConditionalOnClass(SsoService.class)
@EnableConfigurationProperties({ErpProperties.class})
@AutoConfigureAfter(JsfAutoConfiguration.class)
@ImportResource("classpath:/META-INF/erp/jsf-consumer.xml")
@ConditionalOnProperty(name = "xxx.erp.enabled", matchIfMissing = true)
public class ErpAutoConfiguration extends WebMvcConfigurerAdapter {

    private final ErpProperties properties;

    @Autowired
    private SsoService ssoService;

    public ErpAutoConfiguration(ErpProperties properties) {
        this.properties = properties;
    }

    @Bean(initMethod = "initialize")
    public SpringSSOInterceptor ssoInterceptor() {

        SpringSSOInterceptor springSSOInterceptor = new SpringSSOInterceptor();
        springSSOInterceptor.setAppDomainName(this.properties.getAppDomainName());
        springSSOInterceptor.setAppHomeUrl(this.properties.getAppHomeUrl());
        springSSOInterceptor.setLoginUrl(this.properties.getLoginUrl());
        springSSOInterceptor.setSsoDomainName(this.properties.getSsoDomainName());
        springSSOInterceptor.setExcludePath(this.properties.getExcludePath());
        springSSOInterceptor.setSsoService(ssoService);

        return springSSOInterceptor;
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(ssoInterceptor());
        registry.addInterceptor(springAuthorizationInterceptor());
    }
}

然后发现,该包的resource/META-INF/spring.factories的配置如下:
它会顺序的加载这些配置,先加载UMP配置(我们的监控拦截器),再是ERP拦截器。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.jd.spring.boot.autoconfigure.ump.UmpAutoConfiguration,\
com.jd.spring.boot.autoconfigure.erp.ErpAutoConfiguration,\
com.jd.spring.boot.autoconfigure.jsf.JsfAutoConfiguration,\
com.jd.spring.boot.autoconfigure.velocity.VelocityLayoutAutoConfiguration

通过查阅,发现如果类被SpringBoot启动类扫描到了,那么它会优先于所有的spring.factories读取的配置类,那么结果就成我一看是那样了。

解决

/**
 * 接入国际化登陆拦截器
 */
@AutoConfigureAfter(ErpAutoConfiguration.class)
public class WebMvcI18nConfiguration extends WebMvcConfigurerAdapter {
    @Resource
    SpringUnionI18nInterceptor manDefaultSpringUnionI18nInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(manDefaultSpringUnionI18nInterceptor).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

因为使用@EnableAutoConfiguration来完成的,不配spring.factories文件,那么不会加载该类。
文件放在/resources/META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.jd.spring.boot.autoconfigure.ump.WebMvcI18nConfiguration

注意:WebMvcI18nConfiguration不能被启动类扫描到啦。(怎么才能不被扫描,往下看)

总结

1.相关注解含义

org.springframework.boot.autoconfigure包下的注解包含:

  • @AutoConfigureAfter:可以指定在什么类加载之后对所修饰的类进行加载。
    在加载ErpAutoConfiguration配置类后加载WebMvcConfiguration配置类
@AutoConfigureAfter(ErpAutoConfiguration.class)
public class WebMvcConfiguration{
}
  • @AutoConfigureBefore:同理,在什么类加载之前对所修饰的类进行加载。
  • @AutoConfigureOrder:设置一个值,值越小,那么其所修饰的类越先加载。
  • @AutoConfigurePackage
  • @EnableAutoConfiguration:加载符合条件的配置类(@Configuration),通过SpringFactoriesLoader读取 resource/META-INF/spring.factories配置文件,加载指定的配置类。
  • @SpringBootApplication:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解组成的复合注解。

前三个注解是不能再普通项目中使用的,这3个注解特地用于autoconfigure类的项目,后面三个可以用于我们自己的项目。

2.如何定制拦截器的排序顺序
1.拦截器在自己的项目中

可以使用@Order注解来修饰配置类,值越小越优先加载,那么拦截器必定先加载。

2.自己的拦截器想要在autoconfigure包中后执行

使用spring.factories文件来进行巧妙的排序。
很多开源的公司都自己实现相关的starter包

  • camel-spring-boot-starter
  • mybatis-spring-boot-starter
  • 等等
    这个项目主要靠pom.xml将所需要的依赖引入进来。同时项目还会有一个 xxx-spring-boot-autoconfigure 项目,这个项目主要写带 @Configuration注解的配置类,,在这个类或者类中带 @Bean 的方法上,可以使用和顺序有关的注解,也就是前面提到的自己不能使用的这部分注解。xxx-spring-boot-autoconfigure 就是这里提到的 autoconfigure 类项目。
    上面的注解只在 AutoConfigurationSorter类中排序时用到了。被排序的这些类,都是通过 xxx-spring-boot-autoconfigure 项目中的 src/resources/META-INF/spring.factories 配置文件获取的,这个文件中的配置内容一般为:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration

SpringBoot只会对这个配置文件里的配置类进行排序,但不要认为写在spring.factories中就会生效(我踩过),生效的前提是,这个类没有配SpringBoot启动类扫描到,扫描到就提前加载了。

3.使用Order注解可以吗

SpringBoot下:
经过测试发现不行,不能指定Configuration类哪一个先加载,哪一个后加载。
Order注解只有对AOP的拦截顺序有效。
具体原因暂时不了解。

3.如何不被启动类扫描

默认情况下,只扫描启动类所在包及其子包下的类。
也可以在启动类上加@ComponentScan(basePackages = "com.lion")注解,规定扫描哪些包。

其他注解

1.@ImportResource:用来导入XML配置文件的。
2.@ConditionalOnProperty:可以用来控制配置类是否生效。

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
    String[] value() default {}; //数组,获取对应property名称的值,与name不可同时使用  
  
    String prefix() default "";//property名称的前缀,可有可无  
  
    String[] name() default {};//数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用  
  
    String havingValue() default "";//可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置  
  
    boolean matchIfMissing() default false;//缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错  
  
    boolean relaxedNames() default true;//是否可以松散匹配,至今不知道怎么使用的  
} 

ConditionalOnProperty使用方法:
通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值。
如果该值为空,则返回false;
如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
如果返回值为false,则该configuration不生效;为true则生效。

参考文章

@ConditionalOnProperty来控制Configuration是否生效
SpringBoot中Order中注解无效
SpringBoot-配置排序依赖技巧
SpringBoot配置扫描其他包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值