Springboot 自动配置 & 自定义Starter
0. Springboot 自动配置
面试中经常被问到:为什么用springboot? 最多的答案是:
1. Springboot 可以用Java配置的方式来配置Bean,省去了许多配置文件。其实Spring本身就可以做这件事情
2. Boot用来做cloud微服务。 其实spring脱离Springboot也可以做微服务。
3. boot内部集成了Tomcat,内部集成了Tomcat。 这个确实是特色,但不是核心
其实Springboot最重要的功能是自动配置。Springboot开启的注解是:@SpringBootApplication,其代码如下:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.core.annotation.AliasFor; @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 { @AliasFor( annotation = EnableAutoConfiguration.class ) Class<?>[] exclude() default {}; @AliasFor( annotation = EnableAutoConfiguration.class ) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "nameGenerator" ) Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
SpringBootConfiguration 也是一个组合注解:(可以说是Configuration的别名)
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.boot; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AliasFor; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
可以看到是三个注解组成的:
(1)org.springframework.context.annotation.ComponentScan
(2)org.springframework.context.annotation.Configuration
(3)org.springframework.boot.autoconfigure.EnableAutoConfiguration
可以看到前两个是Spring自带的,和boot无关。所以Springboot最核心的在第三个注解EnableAutoConfiguration 上。这个注解内部引入了:AutoConfigurationImportSelector
其源码如下:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
1. AutoConfigurationImportSelector 这个类实现的主要接口如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
2. 在服务启动过程中会调用下面方法:
调用过程如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> { return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName()); }); AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); Iterator var4 = autoConfigurationEntry.getConfigurations().iterator(); while(var4.hasNext()) { String importClassName = (String)var4.next(); this.entries.putIfAbsent(importClassName, annotationMetadata); } }
3. 接下来调用org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { AnnotationAttributes attributes = this.getAttributes(annotationMetadata); List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); configurations = this.removeDuplicates(configurations); Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = this.getConfigurationClassFilter().filter(configurations); this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } }
4. 接下来调用org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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; }
5. 接下来调用:org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }
factoryTypeName 如下:
6. 接下来调用org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
这个就是加载META-INF/spring.factories文件中的配置信息, 读取到的信息如下:
7. 接下来继续getOrDefault 获取到org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置的类进行注入
1. mybatis-plus-boot-starter分析
我们在开发中经常会用到其自动配置,比如我们配置mybatis-plus,可能会加入如下pom:
<!-- spring-boot整合mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>2.3</version> </dependency>
1. 查看这个mybatis-plus-boot-starter的pom信息
<?xml version="1.0" encoding="UTF-8"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>2.3</version> <name>mybatis-plus</name> <description>An enhanced toolkit of Mybatis to simplify development.</description> <url>https://github.com/baomidou/mybatis-plus</url> <licenses> <license> <name>The Apache License, Version 2.0</name> <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> </license> </licenses> <developers> <developer> <id>baomidou</id> <name>hubin</name> <email>jobob@qq.com</email> </developer> </developers> <scm> <connection>scm:git@github.com:Codearte/gradle-nexus-staging-plugin.git</connection> <developerConnection>scm:git@github.com:Codearte/gradle-nexus-staging-plugin.git</developerConnection> <url>https://github.com/baomidou/mybatis-plus</url> </scm> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>1.5.13.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <version>1.5.13.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <version>1.5.13.RELEASE</version> <scope>compile</scope> <optional>true</optional> </dependency> </dependencies> </project>
2. 查看其包内的信息如下:
3. 查看Spring.factories 文件 (这个实际就是让MybatisPlusAutoConfiguration 配置类自动注入)
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusAutoConfiguration
4. MybatisPlusAutoConfiguration 这个类就是注册一些必须的Bean
package com.baomidou.mybatisplus.spring.boot.starter; import com.baomidou.mybatisplus.MybatisConfiguration; import com.baomidou.mybatisplus.MybatisXMLLanguageDriver; import com.baomidou.mybatisplus.entity.GlobalConfiguration; import com.baomidou.mybatisplus.incrementer.IKeyGenerator; import com.baomidou.mybatisplus.mapper.ISqlInjector; import com.baomidou.mybatisplus.mapper.MetaObjectHandler; import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean; import java.util.Iterator; import java.util.List; import javax.annotation.PostConstruct; import javax.sql.DataSource; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.mapper.ClassPathMapperScanner; import org.mybatis.spring.mapper.MapperFactoryBean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @Configuration @ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class}) @ConditionalOnBean({DataSource.class}) @EnableConfigurationProperties({MybatisPlusProperties.class}) @AutoConfigureAfter({DataSourceAutoConfiguration.class}) public class MybatisPlusAutoConfiguration { private static final Log logger = LogFactory.getLog(MybatisPlusAutoConfiguration.class); private final MybatisPlusProperties properties; private final Interceptor[] interceptors; private final ResourceLoader resourceLoader; private final DatabaseIdProvider databaseIdProvider; private final List<ConfigurationCustomizer> configurationCustomizers; private final ApplicationContext applicationContext; public MybatisPlusAutoConfiguration(MybatisPlusProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, ApplicationContext applicationContext) { this.properties = properties; this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable(); this.resourceLoader = resourceLoader; this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable(); this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable(); this.applicationContext = applicationContext; } @PostConstruct public void checkConfigFileExists() { if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); } } @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); 。。。
5. MybatisPlusProperties 是默认的配置文件:
@ConfigurationProperties( prefix = "mybatis-plus" ) public class MybatisPlusProperties { public static final String MYBATIS_PLUS_PREFIX = "mybatis-plus"; private String configLocation; private String[] mapperLocations; private String typeAliasesPackage; private String typeEnumsPackage; private String typeHandlersPackage; private boolean checkConfigLocation = false; private ExecutorType executorType; private Properties configurationProperties; @NestedConfigurationProperty private GlobalConfig globalConfig; @NestedConfigurationProperty private MybatisConfiguration configuration; 。。。 }
2. 定义自己的Starter-自动启动nettyServer
其实有个不成文的约定。Spring官方的是以spring-boot-starter-{xxx}命名,spring-boot-starter-data-redis;自定义的以{xxx}-spring-boot-starter命名,mybatis-plus-boot-starter。
1. 新建项目netty-spring-boot-starter
2. pom如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>cn.qz.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>netty-spring-boot-starter</artifactId> <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.11.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies> </project>
3. 主要类:
Netty相关类:
package cn.xm.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class NettyServer { private static final Logger log = LoggerFactory.getLogger(NettyServer.class); private final int port; public NettyServer(NettyProperties properties) { this.port = properties.getPort(); try { this.start(); } catch (Exception exception) { log.error("NettyServer start error", exception); } } private void start() throws Exception { // 修改bossGroup的数量,2线程足够用 EventLoopGroup bossGroup = new NioEventLoopGroup(2); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap sb = new ServerBootstrap(); sb.option(ChannelOption.SO_BACKLOG, 1024); sb.group(workerGroup, bossGroup) // 绑定线程池 .channel(NioServerSocketChannel.class) // 指定使用的channel .localAddress(this.port)// 绑定监听端口 .childHandler(new NettyServerInitializer()); ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定 log.info(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress()); cf.channel().closeFuture().sync(); // 关闭服务器通道 } finally { workerGroup.shutdownGracefully().sync(); // 释放线程池资源 bossGroup.shutdownGracefully().sync(); } } }
package cn.xm.netty; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpServerCodec; public class NettyServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { // 向管道加入处理器 // 得到管道 ChannelPipeline pipeline = ch.pipeline(); // 加入一个netty 提供的httpServerCodec codec =>[coder - decoder] // HttpServerCodec 说明 //1. HttpServerCodec 是netty 提供的处理http的 编-解码器 pipeline.addLast("MyHttpServerCodec", new HttpServerCodec()); //2. 增加一个自定义的handler pipeline.addLast("MyTestHttpServerHandler", new NettyHttpServerHandler()); System.out.println("ok~~~~"); } }
package cn.xm.netty; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; import io.netty.util.CharsetUtil; import java.net.URI; public class NettyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> { //channelRead0 读取客户端数据 @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { System.out.println("对应的channel=" + ctx.channel() + " pipeline=" + ctx .pipeline() + " 通过pipeline获取channel" + ctx.pipeline().channel()); System.out.println("当前ctx的handler=" + ctx.handler()); //判断 msg 是不是 httprequest请求 if (msg instanceof HttpRequest) { System.out.println("ctx 类型=" + ctx.getClass()); System.out.println("pipeline hashcode" + ctx.pipeline().hashCode() + " TestHttpServerHandler hash=" + this.hashCode()); System.out.println("msg 类型=" + msg.getClass()); System.out.println("客户端地址" + ctx.channel().remoteAddress()); //获取到 HttpRequest httpRequest = (HttpRequest) msg; //获取uri, 过滤指定的资源 URI uri = new URI(httpRequest.uri()); if ("/favicon.ico".equals(uri.getPath())) { System.out.println("请求了 favicon.ico, 不做响应"); return; } //回复信息给浏览器 [http协议] ByteBuf content = Unpooled.copiedBuffer("hello, 我是服务器", CharsetUtil.UTF_8); //构造一个http的相应,即 httpresponse FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); //将构建好 response返回 ctx.writeAndFlush(response); } } }
配置相关:
package cn.xm.netty; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties( prefix = "netty" ) public class NettyProperties { public static final String NETTY_PREFIX = "netty"; private int port = 6969; public int getPort() { return port; } public void setPort(int port) { this.port = port; } }
package cn.xm.netty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties({NettyProperties.class}) // 指定在某个类之后进行IoC注入 //@AutoConfigureAfter() public class NettyAutoConfiguration { @Bean public NettyServer nettyServer(NettyProperties properties) { return new NettyServer(properties); } }
4. 接下来在resources目录下META-INF目录下新建spring.factories
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ cn.xm.netty.NettyAutoConfiguration
这种方式会自动加载配置,也就是引入Jar包就会加载。(在之前学习Springboot的时候了解过,Springboot自动配置的原理就是扫面META-INF/spring.factories 文件,加载AutoConfiguration文件)
查看编译后的target目录如下:
spring-configuration-metadata.json内容如下:
{ "groups": [ { "name": "netty", "type": "cn.xm.netty.NettyProperties", "sourceType": "cn.xm.netty.NettyProperties" } ], "properties": [ { "name": "netty.port", "type": "java.lang.Integer", "sourceType": "cn.xm.netty.NettyProperties", "defaultValue": 6969 } ], "hints": [] }
5. 还有一种方式就是手动载入Netty配置:
这个也有两种配置方式:
第一种就是不存在上面spring.factories 文件,这时候可以通过@EnableXXX引入自动配置的类,比如:
package cn.xm.netty; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({NettyAutoConfiguration.class}) public @interface EnableNettyServer { }
在另一个服务使用的使用需要通过@EnableNettyServer 注解引入NettyAutoConfiguration 自动配置类。
第二种是存在spring.factories 文件,这时候也可以通过一个标记做成手动开启:
(1) 修改NettyAutoConfiguration
package cn.xm.netty; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties({NettyProperties.class}) @ConditionalOnBean(NettyServerMarkerConfiguration.Marker.class) // 指定在某个类之后进行IoC注入 //@AutoConfigureAfter() public class NettyAutoConfiguration { @Bean public NettyServer nettyServer(NettyProperties properties) { return new NettyServer(properties); } }
增加类NettyServerMarkerConfiguration:
package cn.xm.netty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration( proxyBeanMethods = false ) public class NettyServerMarkerConfiguration { public NettyServerMarkerConfiguration() { } @Bean public NettyServerMarkerConfiguration.Marker nettyServerMarkerBean() { return new NettyServerMarkerConfiguration.Marker(); } class Marker { Marker() { } } }
修改EnableNettyServer:
package cn.xm.netty; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({NettyServerMarkerConfiguration.class}) public @interface EnableNettyServer { }
这时候其他项目通过@EnableNettyServer 注解引入Netty相关配置。
6. 使用:
(1) 引入依赖:
<dependency> <groupId>cn.qz.cloud</groupId> <artifactId>netty-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
(2) @EnableNettyServer开启Netty
package cn.qz.cloud; import cn.qz.lock.anno.EnableDistributedLock; import cn.xm.netty.EnableNettyServer; import cn.xm.netty.NettyAutoConfiguration; import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; @SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker @EnableHystrix // 开启分布式锁注解(此注解实际为了将bean注入到Spring,如果包名可以被扫描到不需要打此注解也可以) @EnableDistributedLock // 开启NettyServer @EnableNettyServer public class PaymentHystrixMain8081 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8081.class, args); } /** * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑 * ServletRegistrationBean因为SpringBoot的默认路径不是 “/hystrix.stream" * 只要在自己的项目里配置上下的servlet就可以了 */ @Bean public ServletRegistrationBean getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; } }
(3) 如果需要修改默认netty启动的端口,可以在application.properties文件通过: netty.port = 1566 修改
其实starter的核心也就是将自定义的AutoConfiguration类注册到Spring中。可以通过@Import、加载到spring.factories文件等方式。
Springboot遵循约定大于配置。一般每个starter都有一个Proties默认配置类和一个AutoConfiguration注册Bean的类,然后将相关的Bean用默认的配置注入到SpringIoC,并且暴露一个通过yml或者properties文件修改的配置(也就是NettyProperties类的方式)。
补充: Springboot 的自动配置还有一些其他的自动配置——
1. dubbo-springboot-autoconfigure 包中的spring.factories 配置文件引入了一些自动配置的类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration,\ org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBindingAutoConfiguration org.springframework.context.ApplicationListener=\ org.apache.dubbo.spring.boot.context.event.OverrideDubboConfigApplicationListener,\ org.apache.dubbo.spring.boot.context.event.WelcomeLogoApplicationListener,\ org.apache.dubbo.spring.boot.context.event.AwaitingNonWebApplicationListener org.springframework.boot.env.EnvironmentPostProcessor=\ org.apache.dubbo.spring.boot.env.DubboDefaultPropertiesEnvironmentPostProcessor org.springframework.context.ApplicationContextInitializer=\ org.apache.dubbo.spring.boot.context.DubboApplicationContextInitializer
2. 自动配置的时机:
(1) EnableAutoConfiguration
这个是在ConfigurationClassPostProcessor 扫描过程中进行处理的。
(2) ApplicationContextInitializer 、 ApplicationListener
1》 获取时机
org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...):
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.addConversionService = true; this.headless = true; this.registerShutdownHook = true; this.additionalProfiles = new HashSet(); this.isCustomEnvironment = false; this.lazyInitialization = false; this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); }
2》调用时机
Spring 启动的run 方法过程中调用org.springframework.boot.SpringApplication#prepareContext -》 org.springframework.boot.SpringApplication#applyInitializers
protected void applyInitializers(ConfigurableApplicationContext context) { Iterator var2 = this.getInitializers().iterator(); while(var2.hasNext()) { ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next(); Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class); Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); initializer.initialize(context); } }
也就是该过程发生在Spring IOC 容器启动之前,比如org.apache.dubbo.spring.boot.context.DubboApplicationContextInitializer: (向容器添加了两个BeanDefinitionRegistryPostProcessor)
public class DubboApplicationContextInitializer implements ApplicationContextInitializer, Ordered { public DubboApplicationContextInitializer() { } public void initialize(ConfigurableApplicationContext applicationContext) { this.overrideBeanDefinitions(applicationContext); } private void overrideBeanDefinitions(ConfigurableApplicationContext applicationContext) { applicationContext.addBeanFactoryPostProcessor(new OverrideBeanDefinitionRegistryPostProcessor()); applicationContext.addBeanFactoryPostProcessor(new DubboConfigBeanDefinitionConflictProcessor()); } public int getOrder() { return -2147483648; } }
(3) EnvironmentPostProcessor 的加载以及调用是在org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent