美文网首页
(四)使用Netty结合springmvc

(四)使用Netty结合springmvc

作者: guessguess | 来源:发表于2021-04-08 19:35 被阅读0次

    之前用netty做了一个简陋的web容器,算是一个demo。
    但是在实际应用里面,肯定都是用类似spring,springmvc做管理容器的。
    网上有一些跟Netty相关的mvc框架,但是没发现像springmvc那么主流的,所以这里就结合Spring,springmvc,netty做一个web容器。

    依赖

    依赖比较简单,其实就是普通的spring-starter 还有feign的组件,还有eureka,熔断器,方便后续使用,集成在里面,也有一些Spring-web相关的依赖需要用到,但是feign的组件里面已经提供了

    <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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.gee</groupId>
      <artifactId>netty-springmvc-demo</artifactId>
      <version>0.0.1-SNAPSHOT</version>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.6.RELEASE</version>
            <relativePath />
            <!-- lookup parent from repository -->
        </parent>
        <properties>
            <java.version>1.8</java.version>
            <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
            <docker.registry>191.0.0.158/ibcloud</docker.registry>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>
    
        <dependencyManagement>
            <dependencies>
                <!-- spring-cloud-dependencies start -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!-- spring-cloud-dependencies end -->
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <!-- netty组件 -->
            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
            </dependency>
            <!-- 时间工具类 start -->
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
            </dependency>
    
            <!-- lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <scope>provided</scope>
            </dependency>
    
            <!-- spring start -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <!-- 单元测试 start -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
    
            <!-- 服务发现 start -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
            </dependency>
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-starter-client</artifactId>
                <version>2.0.0</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <!-- 服务发现 end -->
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>${java.version}</source>
                        <target>${java.version}</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <skip>true</skip>
                    </configuration>
                </plugin>
                <!-- 指定maven编译的jdk的版本 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <skip>true</skip>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    

    yml配置文件

    server:
      port: 9413
    
    spring:
      application:
        name: netty-spring-mvc-demo
      profiles:
        active:
        - local
          
    netty: 
      server: 
        port: ${nettyport:9527}
        host: ${nettyhost:127.0.0.1}
        
    eureka:
      instance:
        lease-renewal-interval-in-seconds: 15
        prefer-ip-address: true
      client:
        serviceUrl:
          defaultZone: ${EUREKA_CLIENT_SERVICEURL_DEFAULTZONE:}
    
    

    项目启动类

    package com.gee;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    import com.gee.netty.NettyServer;
    
    
    
    @SpringBootApplication
    @EnableConfigurationProperties
    @EnableFeignClients
    public class NettyWebApplication implements CommandLineRunner{
        public static void main(String[] args) {
            SpringApplication.run(NettyWebApplication.class, args);
        }
        
        @Autowired
        private NettyServer nettyServer;
        
        @Override
        public void run(String... args) throws Exception {
            nettyServer.start();
        }
    }
    

    这里也没有太特殊的地方,其实就是实现了CommandLineRunner,可以在容器启动后,启动Netty服务端。

    构建Netty服务

    1.先构建最基本的Netty服务配置

    这个配置的话,大家可以自己写死,或者是配置在配置文件里面

    @Configuration
    @ConfigurationProperties(prefix = "netty.server")
    @Data
    public class NettyServerConfig {
        private Integer port;
        private String host;
    }
    

    2.构建自定义的DispatcherServlet用于处理请求, 处理请求的核心。

    为什么要弄一个类,去继承DispatcherServlet。
    答案比较简单,因为DispatcherServlet中的doService方法是protected的,所以只能继承子类去调用。
    DispatcherServlet是Springmvc处理请求的核心。
    另外springmvc本身也是主流的组件,自己去写一个servlet太费劲了。
    另外我们还看到,构建自定义的DispatcherServlet的时候,需要传入一个web容器,因为servlet初始化的时候,需要传入一个容器。下面再讲一下容器是如何构建的。

    package com.gee.netty.handler;
    
    
    import javax.servlet.ServletException;
    
    import javax.servlet.http.HttpServletRequest;
    
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
    import org.springframework.web.servlet.DispatcherServlet;
    public class CustomDispatcherServlet extends DispatcherServlet{
        private static final long serialVersionUID = -5960477391619626506L;
        
        public CustomDispatcherServlet(AnnotationConfigWebApplicationContext webApplicationContext)
                throws ServletException {
            super(webApplicationContext);
            this.init(webApplicationContext.getServletConfig());
        }
    
        public void doService(HttpServletRequest request, HttpServletResponse response) {
            try {
                super.doService(request, response);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    3.构建web容器。用于servlet的初始化。

    因为Servlet本身,单例就满足需求了,所以直接注入到容器里面去。
    先弄一个配置类,用于扫描Mvc的组件, 在构建完容器后,将其注册到容器中,随后进行刷新。

    @Configuration
    @ComponentScan("com.gee.web.controller")
    public class WebAppConfig {
    
    }
    
    package com.gee.netty;
    
    import javax.servlet.ServletException;
    
    import org.springframework.beans.factory.BeanCreationException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.mock.web.SpringBootMockServletContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.mock.web.MockServletConfig;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
    
    import com.gee.netty.handler.CustomDispatcherServlet;
    import com.gee.netty.handler.HttpHandler;
    import com.gee.web.WebAppConfig;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelOption;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.http.HttpObjectAggregator;
    import io.netty.handler.codec.http.HttpRequestDecoder;
    import io.netty.handler.codec.http.HttpResponseEncoder;
    import lombok.extern.slf4j.Slf4j;
    
    @Component
    @Slf4j
    public class NettyServer {
        
        @Autowired
        private NettyServerConfig serverConfig;
            
        @Bean
        public CustomDispatcherServlet customDispatcherServlet() {
            AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
            webApplicationContext.register(WebAppConfig.class);
            webApplicationContext.refresh();
            SpringBootMockServletContext sevletContext = new SpringBootMockServletContext("/");
            sevletContext.setServletContextName("mokServlet");
            MockServletConfig sevletConfig = new MockServletConfig(sevletContext);
            webApplicationContext.setServletConfig(sevletConfig);
            webApplicationContext.setServletContext(sevletContext);
            try {
                创建完容器后,将容器传入到自定义的servlet中
                return new CustomDispatcherServlet(webApplicationContext);
            } catch (ServletException e) {
                throw new BeanCreationException("创建CustomDispatcherServlet的时候失败");
            }
        }
        
        public void start() {
            // 用于处理连接事件
            EventLoopGroup boss = new NioEventLoopGroup();
            // 用于处理读写事件
            EventLoopGroup work = new NioEventLoopGroup();
            ServerBootstrap sb = new ServerBootstrap();
            sb.group(boss, work);
            // 设置处理的通道类型
            sb.channel(NioServerSocketChannel.class);
            sb.option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
            // 设置对于客户端连接的处理器
            sb.childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    //对http请求进行解码
                    ch.pipeline().addLast(new HttpRequestDecoder());
                    //对响应进行编码
                    ch.pipeline().addLast(new HttpResponseEncoder());
                    ch.pipeline().addLast(new HttpObjectAggregator(65536));
                    //这个handler是自定义的Handler,用于处理请求的。
                    ch.pipeline().addLast(new HttpHandler());
    
                }
            });
            ChannelFuture cf = null;
            try {
                cf = sb.bind(serverConfig.getHost(), serverConfig.getPort()).sync();
                if(cf.isDone()) {
                    log.info("服务端启动成功");
                }
                cf.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.error("服务端启动或者关闭时出现异常");
            } finally {
                boss.shutdownGracefully();
                work.shutdownGracefully();
            }
        }
    }
    

    4.构建自定义的处理器,用于处理channel的读写相关。

    由于这里我们只需要响应,所以比较简单。

    package com.gee.netty.handler;
    
    
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.handler.codec.http.DefaultFullHttpResponse;
    import io.netty.handler.codec.http.FullHttpRequest;
    import io.netty.handler.codec.http.FullHttpResponse;
    import io.netty.handler.codec.http.HttpResponseStatus;
    import io.netty.handler.codec.http.HttpVersion;
    import io.netty.util.CharsetUtil;
    
    import org.apache.commons.lang.StringUtils;
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.mock.web.MockHttpServletResponse;
    
    import com.gee.utils.ApplicationContextUtils;
    
    
    public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest>{
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullRequest) throws Exception {
            MockHttpServletRequest request = new MockHttpServletRequest();
            request.setMethod(fullRequest.method().name());
            request.setContentType(fullRequest.headers().get("Content-Type"));
            request.setRequestURI(fullRequest.uri());
    
            MockHttpServletResponse response = new MockHttpServletResponse();
            通过工具类从容器中获取CustomDispatcherServlet单例,用于处理请求,后面再讲容器的工具类的构建。=======================================================
            ApplicationContextUtils.getBean(CustomDispatcherServlet.class).doService(request, response);
            HttpResponseStatus status = HttpResponseStatus.valueOf(response.getStatus());
            String result = response.getContentAsString();
            result = StringUtils.isEmpty(result) ? "" : result;
    
            FullHttpResponse fullResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
                    Unpooled.copiedBuffer(result, CharsetUtil.UTF_8));
            fullResponse.headers().set("Content-Type", "text/json;charset=UTF-8");
            fullResponse.headers().set("Access-Control-Allow-Origin", "*");
            fullResponse.headers().set("Access-Control-Allow-Headers",
                    "Content-Type,Content-Length, Authorization, Accept,X-Requested-With,X-File-Name");
            fullResponse.headers().set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
            fullResponse.headers().set("Content-Length", Integer.valueOf(fullResponse.content().readableBytes()));
            fullResponse.headers().set("Connection", "keep-alive");
            ChannelFuture writeFuture = ctx.writeAndFlush(fullResponse);
            writeFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }
    

    5工具类

    package com.gee.utils;
    
    
    import java.lang.annotation.Annotation;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.ApplicationEvent;
    import org.springframework.stereotype.Component;
    
    
    @Component
    public class ApplicationContextUtils implements ApplicationContextAware{
        //用于封装成员变量
        private ApplicationContext applicationContext;
        //封装一个静态变量用于静态方法的调用
        private static ApplicationContext STATIC_APPLICATION_CONTEXT;
        
        //为什么实现这个接口,
        //ApplicationContextAwarePostProcessor会判断类是否是ApplicationContextAware的子类
        //如是,则会将容器作为成员变量,通过SET方法封装到成员变量
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
            STATIC_APPLICATION_CONTEXT = applicationContext;
        }
        
        public static <T> T getBean(Class<T> classT) {
            if(null == STATIC_APPLICATION_CONTEXT) {
                return null;
            }
            return STATIC_APPLICATION_CONTEXT.getBean(classT);
        }
        
        public static Object getBean(String beanName) {
            if(null == STATIC_APPLICATION_CONTEXT) {
                return null;
            }
            return STATIC_APPLICATION_CONTEXT.getBean(beanName);
        }
        
           /**
         * 获取接口的所有实现类
        **/
        public static <T> List<T> getBeanList(Class<T> classT) {
            String beanNames[] = STATIC_APPLICATION_CONTEXT.getBeanNamesForType(classT);
            if(null == beanNames) {
                return null;
            }
            List<T> beanList = new ArrayList<T>();
            Object bean;
            for(String beanName : beanNames) {
                bean = getBean(beanName);
                if(null != bean) {
                    beanList.add((T)bean);
                }
            }
            return beanList;
        }
        
        public static void publishEvent(ApplicationEvent event) {
            STATIC_APPLICATION_CONTEXT.publishEvent(event);
        }
        
        public static <T> T getBean(String beanName, Class<T> claz) {
            return STATIC_APPLICATION_CONTEXT.getBean(beanName, claz);
        }
        
        public static Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
            return STATIC_APPLICATION_CONTEXT.getBeansWithAnnotation(annotationType);
        }
        
        public static Class getType(String beanName) {
            return STATIC_APPLICATION_CONTEXT.getType(beanName);
        }
        
        public static String getActiveProfile() {
            return STATIC_APPLICATION_CONTEXT.getEnvironment().getActiveProfiles()[0];
        }
        
        public static List<String> getBeanNams() {
            return Arrays.asList(STATIC_APPLICATION_CONTEXT.getBeanDefinitionNames());
        }
    }
    
    

    6.写一个controller用于测试

    package com.gee.web.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    
    @RestController
    @RequestMapping("/test")
    public class TestController {
        @GetMapping("/hello")
        public String get() {
            return "helloworld";
        }
    }
    
    运行结果图

    相关文章

      网友评论

          本文标题:(四)使用Netty结合springmvc

          本文链接:https://www.haomeiwen.com/subject/seudfltx.html