虽然自定义的starter与版本无关,但还是说明一下版本
SpringBoot 版本2.1.4.RELEASE
由于官方提供的starter,命名格式为spring-boot-starter-xxx,为与官方的starter区分开来,官方建议自定义的starter命名方式为xxx-spring-boot-starter,也仅仅是建议。
SpringBoot官方的starter,和自定义的starter,基本都是利用java的SPI思想。在SpringBoot的自动装配过程中,最终会加载classpath目录下所有的META-INF/spring.factories文件,查看任一个starter,应该都有该文件。加载spring.factories文件的代码定位:
1、SpringApplication构造函数
2、this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
3、getSpringFactoriesInstances
4、SpringFactoriesLoader.loadFactoryNames(静态方法)
5、loadSpringFactories
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") :ClassLoader.getSystemResources("META-INF/spring.factories");
在spring.factories文件中查看所有的ApplicationContextInitializer.class和ApplicationListener.class文件。之所以说是基本是利用SPI思想,是因为不配置spring.factories文件,也是可以完成starter开发的。
在spring.factories文件中配置EnableAutoConfiguration,如下所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wit.sc.support.configuration.SupportAutoConfiguration
其中SupportAutoConfiguration是starter配置文件的类限定名,多个之间逗号分割。
定义一个Enable注解,如下所示,类似@EnableEurekaClient、@EnableAsync等注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(EnableSupportImportSelector.class)
@Documented
public @interface EnableSupport {
@AliasFor("autowired")
boolean value() default true;
/**
* 是否注入
* @return
*/
@AliasFor("value")
boolean autowired() default true;
}
在注解中使用Import注解(spring4.2之后的注解),导入相应的bean到spring容器中,该类需要实现ImportSelector接口,在该接口中让SupportAutoConfiguration配置类生效,到目前为止,和在spring.factories文件中配置此类的效果是一样的。
public class EnableSupportImportSelector implements ImportSelector {
private static final Log logger = LogFactory.getLog(EnableSupportImportSelector.class);
/**
* support配置类
*/
public static final String SUPPORT_DEFAULT_CONFIGURATION = "com.wit.sc.support.configuration.SupportAutoConfiguration";
/**
* support启动配置
*/
public static final String SUPPORT_ENABLE_ANNOTATION = "com.wit.sc.support.configuration.annotation.EnableSupport";
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
MultiValueMap<String, Object> valueMap = importingClassMetadata.getAllAnnotationAttributes(SUPPORT_ENABLE_ANNOTATION);
List<Object> enableFalgList = valueMap.get("value");
boolean enableFlag = (boolean) enableFalgList.get(0);
if(!enableFlag) {
return new String[]{};
}
Set<String> configuration = new HashSet<>();
configuration.add(SUPPORT_DEFAULT_CONFIGURATION);
String[] configComponent =new String[configuration.size()];
configuration.toArray(configComponent);
return enableFlag ? new String[]{SUPPORT_DEFAULT_CONFIGURATION} : new String[]{};
}
}
这样使用虽然比较麻烦,但是starter可以控制,也就是通过以设置参数,让starter生效或失效。比如设置value设置为false,即可让start失效或者不加注解,而配置spring.factories,只要导入相应的包,一定会生效,不想用的办法,只能删除starter包
但似乎越来越多的starter使用的是这种方式,这种方式的简单,也容易控制,相当于结合了前面两种的优势,具体实现如下:
···
1、配置spring.factories
2、定义Enable注解,不用实现ImportSelector接口的复杂逻辑,只要通过注解导入的配置类,将一个bean注入到spring容器中即可
···
以配置中心的注解@EnableConfigServer为例。
1、spring.factories配置EnableAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.cloud.config.server.config.ConfigServerAutoConfiguration
2、定义Enable注解,引入配置类,在配置中将Mark注入到spring容器,mark不用实现任何逻辑
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ConfigServerConfiguration.class})
public @interface EnableConfigServer {
}
@Configuration
public class ConfigServerConfiguration {
public ConfigServerConfiguration() {
}
@Bean
public ConfigServerConfiguration.Marker enableConfigServerMarker() {
return new ConfigServerConfiguration.Marker();
}
class Marker {
Marker() {
}
}
}
3、用ConditionalOnBean注解实现开关的效果
starter有以下功能
1、提供公共接口。
2、为引入starter的模块,提供被component注解的单例
3、实现aop切面,完成系统日志的功能
4、添加过滤器,实现公共的逻辑处理
5、如果确定引入starter的服务的spring容器中,会有某一类型的bean,starter模块也可以引用(通过Resource注解),即starter和引入他的应用之,bean是可以相互注入的
@ComponentScan(basePackages = {"com.wit.sc.support"})
@EnableConfigurationProperties(value = SupportConstant.class)
public class SupportAutoConfiguration {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private SupportConstant supportConstant;
@Bean
public SupportCommandLineRunner supportCommandLineRunner() {
return new SupportCommandLineRunner(this.supportConstant);
}
@Bean
public RequestContextListener requestContextListenerBean() {
return new RequestContextListener();
}
/**
* 从filters变量中获取的过滤器名称为pioneerFilter
* @return
*/
@Bean
public FilterRegistrationBean pioneerFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new PioneerFilter());
FilterConstant pioneerFilterConstant = new FilterConstant();
if(!CollectionUtils.isEmpty(supportConstant.getFilters())) {
pioneerFilterConstant = supportConstant.getFilters().get(PioneerFilter.FILTE_RNAME);
}
registration.setEnabled(pioneerFilterConstant.isEnable());
if(!StringUtils.isEmpty(pioneerFilterConstant.getFilterName())) {
//设置过滤器名称
registration.setName(pioneerFilterConstant.getFilterName());
}
if(!StringUtils.isEmpty(pioneerFilterConstant.getPattern())) {
//设置过滤器拦截路径
List<String> urlPattern = Arrays.asList(StringUtils.split(pioneerFilterConstant.getPattern(), FilterConstant.COMMA));
registration.setUrlPatterns(urlPattern);
}
if(pioneerFilterConstant.getOrder() != null) {
registration.setOrder(pioneerFilterConstant.getOrder());
}
Map<String, String> properties = pioneerFilterConstant.getProperties();
if(!CollectionUtils.isEmpty(properties)) {
registration.setInitParameters(properties);
}
return registration;
}
}
EnableAutoConfiguration是整个starter的关键,通过这个文件,可以使用注解@EnableConfigurationProperties导入其他的属性文件,也可以通过注解@ComponentScan扩大spring容器扫描包的范围,还可以直接将bean注入到spring容器中。
由于注解ComponentScan,让在starter中定义接口成为可能。在该文件中注入bean,还可以添加过滤器等。
过滤器可以用来拦截请求的特定参数,比如分页参数,分页查询的pageSize不得大于50,可以设置在过滤器中,而不用每个接口都去做逻辑判断。
但这是starter,会被很多服务使用的,可能设置的最大分页50,不符合其他系统的要求,必须可以进行覆盖设置。
而EnableConfigurationProperties导入的类,就可以存放属性。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
有了这两个依赖,让引用starter的人知道,你这个starter有哪些属性,在设置属性时,会有提示。效果如下图所示
提示括号中的描述,就是属性对应的注释,毕竟,别人导入jar包,使用的时,去看源码会显得不太方便,最好是能通过注释,让使用的人知道如何使用,属性是什么含义。
被EnableConfigurationProperties导入的属性类代码如下
@ConfigurationProperties(prefix = "support")
public class SupportConstant {
private Method method = new Method();
/**
* filter
* PioneerFilter -> pioneerFilter
* support.filters[pioneerFilter].filterName=pioneerFilter
* support.filters[pioneerFilter].enable=false
* support.filters[pioneerFilter].properties[pageSize]=51
* support.filters[pioneerFilter].properties[whiteUrls]=127.0.0.1,192.168.1.1
*/
private Map<String, FilterConstant> filters = new HashMap<>();
}
建议
参数尽量设置默认值,这也是springboot核心思想,约定大于配置。比如有一个变量,需要根据实际情况,确定参数设置成什么值最好,让别人尽量可以不配置
一个参数就可以使用这个starter模块,而当别人发现默认的配置无法满足自己要求的时候,自己又可以进行设置。比如有一个参数pageSize,默认设置成100,
如果用户配置了,会默认覆盖,不用做任何逻辑。我们先做 int pageSize = 100,使用者后做pageSize=50,所以是可以覆盖的。如果是整型,不设置初始化
都无法使用,难道非要用户设置一个值,程序才可以运行吗,这就违背了springboot的初衷。比如引入redis模块,甚至不用设置一个参数,就可以使用,
极其方便
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
自定义的starter结构如下图所示:
实现了切面、过滤器、自定义日志入库、CommandLineRunner、接口等。实现了两个接口、只要引入了这个starter的,都会含有这两个接口,也可以通过
@Autowired
InterfaceHandler interfaceHandler;
引入starter中的实例。
@RestController
public class SupportController {
@Autowired
InterfaceHandler interfaceHandler;
/**
* 返回所有接口
* @param request
* @return
*/
@GetMapping("interfaces")
public Object interfaces(HttpServletRequest request) {
ServletContext servletContext = request.getSession().getServletContext();
return interfaceHandler.getAllRequestToMethodItems(servletContext);
}
/**
* 返回所有接口路径
* @param request
* @return
*/
@GetMapping("interfaceUrls")
public Object interfaceUrls(HttpServletRequest request) {
ServletContext servletContext = request.getSession().getServletContext();
return interfaceHandler.getAllInterfaceUrls(servletContext);
}
}
接口访问效果如下图所示
两个接口的功能是查询当前应用的所有接口,可见也包含interfaceUrls和interfaces接口。
自定义starter github地址(第二种方式)
手机扫一扫
移动阅读更方便
你可能感兴趣的文章