背景
最近在做运营端的国际化,需要接入国际化拦截器(i18n拦截器),该拦截器用于向Cookie写入中文或其他语言的(zh_CN,en_US),当然该拦截器还要很多功能,没时间看源码,不是很了解。
该拦截器可以通过url参数将cookie信息写入为需要切换的语言,也可直接修改cookie值。
为了保证灰度,它会从配置中心获取Erp列表名称,如果在里头才会切换cookie语言。
问题(踩过的坑)
- 如果你希望在SpringBoot中配置拦截器调用顺序
- 如果你发现你的拦截器总是先执行,maven引入的拦截器总是后执行(配置依赖顺序)
- 如果你想了解一些springboot相关注解
直接看总结也可以方便查看你所需要了解的。
我是这样配置该拦截器的,将Interceptor加入了拦截器链中,需要用到的XML也都引入了,可是我发送请求?language=en_US
时发现cookie写入不成功。
/**
* 接入国际化登陆拦截器
*/
@Configuration
public class WebMvcI18nConfiguration extends WebMvcConfigurerAdapter {
@Resource
SpringUnionI18nInterceptor manDefaultSpringUnionI18nInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
(manDefaultSpringUnionI18nInterceptor).addPathPatterns("/**");
(registry);
}
}
排查
我这边发现每次都是manDefaultSpringUnionI18nInterceptor
该国际化拦截器先执行,springSSOInterceptor
该erp拦截器后执行。
先走erp拦截器,把需要用到的东西放到loginContext上下文中,然后再走i18n拦截器(通过灰度名单来控制是否展示国际化页面,()来获取erp名,如果先i18n拦截器,那么永远不能让国际化拦截器将cookie的语言key-value值更改。
我们这边重新实现了一个类似于spring-boot-starter
的jar包,通过MAVEN方式导入。它会将spring-boot-autoconfigure
包下载下来,有哪些东西呢,往下看:
先看下将ERP拦截器如何加载进拦截器链的,跟我配的一样。(具体实现忽视即可:删掉了一些创建bean的方法,并将一些单词改为xxx了)
// erp拦截器
@Configuration
@ConditionalOnClass()
@EnableConfigurationProperties({})
@AutoConfigureAfter()
@ImportResource("classpath:/META-INF/erp/")
@ConditionalOnProperty(name = "", matchIfMissing = true)
public class ErpAutoConfiguration extends WebMvcConfigurerAdapter {
private final ErpProperties properties;
@Autowired
private SsoService ssoService;
public ErpAutoConfiguration(ErpProperties properties) {
= properties;
}
@Bean(initMethod = "initialize")
public SpringSSOInterceptor ssoInterceptor() {
SpringSSOInterceptor springSSOInterceptor = new SpringSSOInterceptor();
(());
(());
(());
(());
(());
(ssoService);
return springSSOInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
(ssoInterceptor());
(springAuthorizationInterceptor());
}
}
然后发现,该包的resource/META-INF/
的配置如下:
它会顺序的加载这些配置,先加载UMP配置(我们的监控拦截器),再是ERP拦截器。
# Auto Configure
=\
,\
,\
,\
通过查阅,发现如果类被SpringBoot启动类扫描到了,那么它会优先于所有的读取的配置类,那么结果就成我一看是那样了。
解决
/**
* 接入国际化登陆拦截器
*/
@AutoConfigureAfter()
public class WebMvcI18nConfiguration extends WebMvcConfigurerAdapter {
@Resource
SpringUnionI18nInterceptor manDefaultSpringUnionI18nInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
(manDefaultSpringUnionI18nInterceptor).addPathPatterns("/**");
(registry);
}
}
因为使用@EnableAutoConfiguration
来完成的,不配文件,那么不会加载该类。
文件放在/resources/META-INF/
# Auto Configure
=\
.WebMvcI18nConfiguration
注意:WebMvcI18nConfiguration不能被启动类扫描到啦。(怎么才能不被扫描,往下看)
总结
1.相关注解含义
包下的注解包含:
- @AutoConfigureAfter:可以指定在什么类加载之后对所修饰的类进行加载。
在加载ErpAutoConfiguration配置类后加载WebMvcConfiguration配置类
@AutoConfigureAfter()
public class WebMvcConfiguration{
}
- @AutoConfigureBefore:同理,在什么类加载之前对所修饰的类进行加载。
- @AutoConfigureOrder:设置一个值,值越小,那么其所修饰的类越先加载。
- @AutoConfigurePackage
- @EnableAutoConfiguration:加载符合条件的配置类(@Configuration),通过SpringFactoriesLoader读取
resource/META-INF/
配置文件,加载指定的配置类。 - @SpringBootApplication:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解组成的复合注解。
前三个注解是不能再普通项目中使用的,这3个注解特地用于autoconfigure类的项目,后面三个可以用于我们自己的项目。
2.如何定制拦截器的排序顺序
1.拦截器在自己的项目中
可以使用@Order
注解来修饰配置类,值越小越优先加载,那么拦截器必定先加载。
2.自己的拦截器想要在autoconfigure包中后执行
使用文件来进行巧妙的排序。
很多开源的公司都自己实现相关的starter包
- camel-spring-boot-starter
- mybatis-spring-boot-starter
- 等等
这个项目主要靠将所需要的依赖引入进来。同时项目还会有一个 xxx-spring-boot-autoconfigure 项目,这个项目主要写带@Configuration
注解的配置类,,在这个类或者类中带@Bean
的方法上,可以使用和顺序有关的注解,也就是前面提到的自己不能使用的这部分注解。xxx-spring-boot-autoconfigure 就是这里提到的 autoconfigure 类项目。
上面的注解只在AutoConfigurationSorter
类中排序时用到了。被排序的这些类,都是通过 xxx-spring-boot-autoconfigure 项目中的src/resources/META-INF/
配置文件获取的,这个文件中的配置内容一般为:
# Auto Configure
=\
SpringBoot只会对这个配置文件里的配置类进行排序,但不要认为写在中就会生效(我踩过),生效的前提是,这个类没有配SpringBoot启动类扫描到,扫描到就提前加载了。
3.使用Order注解可以吗
SpringBoot下:
经过测试发现不行,不能指定Configuration类哪一个先加载,哪一个后加载。
Order注解只有对AOP的拦截顺序有效。
具体原因暂时不了解。
3.如何不被启动类扫描
默认情况下,只扫描启动类所在包及其子包下的类。
也可以在启动类上加@ComponentScan(basePackages = "")
注解,规定扫描哪些包。
其他注解
1.@ImportResource
:用来导入XML配置文件的。
2.@ConditionalOnProperty
:可以用来控制配置类是否生效。
@Retention()
@Target({ , })
@Documented
@Conditional()
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用来从中读取某个属性值。
如果该值为空,则返回false;
如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
如果返回值为false,则该configuration不生效;为true则生效。
参考文章
@ConditionalOnProperty来控制Configuration是否生效
SpringBoot中Order中注解无效
SpringBoot-配置排序依赖技巧
SpringBoot配置扫描其他包