zoukankan      html  css  js  c++  java
  • 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类的方式)。

      

    AutoConfigurationImportSelector
    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    CF 319C
    日常---区域赛临近
    poj 3728 The merchant 倍增lca求dp
    zoj 3742 Delivery 好题
    zoj 3717 Balloon 2-sat
    CF 163E. e-Government ac自动机+fail树+树状数组
    CF 335B
    hdu 4739 状压DP
    hdu 4738 桥
    Hibernate中的继承映射
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/14503087.html
Copyright © 2011-2022 走看看