zoukankan      html  css  js  c++  java
  • 使用 Netty 实现一个 MVC 框架

    NettyMVC

    上面介绍 Netty 能做是什么时我们说过,相比于 SpringMVC 等框架,Netty 没提供路由等功能,这也契合和 Netty 的设计思路,它更贴近底层。下面我们在 Netty Http 的基础上加入路由和 IOC 等功能,来实现一个 MVC 框架。

    NettyMVC 是一个 MVC 框架,它实现了 IOC 容器的相关功能。

    1. 支持 @Controller,@RequestParam,@RequestMapping 等 MVC 注解。
    2. 支持 @Service,@Repositry,@Autowired 等 IOC 注解。
    3. URI 路由解析,参数映射。
    4. Request 中支持多种参数类型,包括基本数据类型,List,Array,Map等等。
      结构图

    如何使用
    在项目中引入 netty-mvc-core 模块。
    在 Maven 项目中的 resources 文件夹下创建 applicationContext.xml, 用来配置 IOC 的包扫描路径。

     <?xml version="1.0" encoding="UTF-8"?>
     <beans>
         <package-scan component-scan="org.test.demo" />
     </beans>
    

    @Controller 对应控制层注解,@Service 对应服务层注解,@Respostry 对应持有层注解,
    @Autowired 做自动注入,@RequestMapping 做路由,@RequestParam 做参数映射。

      @Controller
      @RequestMapping("/user")
      public class UserController {
    
          @Autowired
          private UserService userService;
    
          @RequestMapping("/getUser")
          public FullHttpResponse getUserById(FullHttpRequest request,@RequestParam("userId") int id,@RequestParam("name") String name){
    
              String res = userService.getUser(id);
              return HttpUtil.constructText(res);
          }
    
      }
    @Service("userService")
      public class UserServiceImpl implements UserService {
    
          @Autowired("userDao")
          private UserDao userDao;
    
          @Override
          public String getUser(int id) {
              return userDao.get(id);
          }
      }
    @Repository
      public class UserDao {
    
          public String get(int id){
              if(id == 1){
                  return "paul";
              }else{
                  return "wang";
              }
          }
      }
    

    代码实现
    既然我们的 MVC 框架是基于 Netty Http 的,我们首先实现 Netty Http 的相关功能。
    服务启动类,单例:

        package com.paul.http;
        import com.paul.ioc.bean.AnnotationApplicationContext;
        import io.netty.bootstrap.ServerBootstrap;
        import io.netty.channel.ChannelFuture;
        import io.netty.channel.EventLoopGroup;
        import io.netty.channel.nio.NioEventLoopGroup;
        import io.netty.channel.socket.nio.NioServerSocketChannel;
    
        public class NettyHttpServer {
    
            private static NettyHttpServer instance = new NettyHttpServer();
    
            private NettyHttpServer(){};
    
            public static NettyHttpServer getInstance(){
                return instance;
            }
    
            private final int port = 8012;
    
            public void start(AnnotationApplicationContext applicationContext) throws InterruptedException {
                //定义两个线程组,事件循环组,可以类比与 Tomcat 就是死循环,不断接收客户端的连接
                // boss 线程组不断从客户端接受连接,但不处理,由 worker 线程组对连接进行真正的处理
                // 一个线程组其实也能完成,推荐使用两个
                EventLoopGroup bossGroup = new NioEventLoopGroup();
                EventLoopGroup workerGroup = new NioEventLoopGroup();
                try {
                    // 服务端启动器
                    ServerBootstrap serverBootstrap = new ServerBootstrap();
                    //group 方法有两个,一个接收一个参数,另一个接收两个参数
                    // childhandler 是我们自己写的请求处理器
                    serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                            .childHandler(new ServerInitializer(applicationContext));
                    //绑定端口
                    ChannelFuture future = serverBootstrap.bind(port).sync();
                    System.out.println("服务端启动,监听端口:8012");
                    //channel 关闭的监听
                    future.channel().closeFuture().sync();
                }finally {
                    //优雅的关闭
                    bossGroup.shutdownGracefully();
                    workerGroup.shutdownGracefully();
                }
    
            }
        }
    
    对应的 Initializer:
    
            public class ServerInitializer extends ChannelInitializer<SocketChannel> {
    
            private AnnotationApplicationContext applicationContext;
    
            public ServerInitializer(AnnotationApplicationContext applicationContext){
                this.applicationContext = applicationContext;
            }
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                //管道,管道里面可以有很多 handler,handler 可以理解为一层层过滤的网
                ChannelPipeline pipeline = socketChannel.pipeline();
                //HttpServerCodec 是 HttpRequestDecoder 和 HttpReponseEncoder 的组合,编码和解码的 handler
                pipeline.addLast("httpServerCodec", new HttpServerCodec());
                pipeline.addLast(new HttpObjectAggregator(10*1024*1024));
                pipeline.addLast("handler", new DispatcherHandler(applicationContext));
            }
        }
        ```
        上面这部分代码除了 DispatcherHandler 没有什么特殊的。通过名字就能知道       
        DispatcherHandler 是我们的核心路由控制类。在看这个类之前我们先定义几个注解,用于 
        IOC 和请求路径的 mapping。
        ```java
        /**
         * 自定义自动注入的注解
         * @author swang18
         *
         */
        @Target({ElementType.FIELD, ElementType.METHOD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface Autowired {
            String value() default "";
        }
    
    @Controller, @Service, @Respostry 与 SpringMVC 注解含义相同,分别对应控制层,业
    务层和持久层。
    
    @RequestMapping 用作请求路径的 mapping,@RequestParam 用作参数的映射。此处代
    码都一样,就不一一列举了。
    
        @Target({ElementType.TYPE,ElementType.METHOD})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public @interface RequestMapping {
            String value() default "";
        }
    

    首先我们实现 IOC 相关功能,将对应类的实例放入容器中保存起来。我们通过解析 applicationContext.xml 的包名,解析包里面有对应注解的类,将这些类的实例加入到容器中,有 @Autowired 注解的地方要把实例注入进来。

    说到容器,很多人可能有点疑惑,到底用什么来实现容器,其实用最简单的 Map 就可以保存名字和对应的实例。

    先看 xml 解析类,将 component-scan 中的包名解析出来:

        public class XmlUtil {
    
            public String handlerXMLForScanPackage(String configuration){
                System.out.println(System.getProperty("user.dir"));//user.dir指定了当前的路径
                System.out.println(configuration);
                InputStream ins = this.getClass().getClassLoader().getResourceAsStream(configuration);
                SAXReader reader = new SAXReader();
                try{
                    Document document = reader.read(ins);
                    Element root = document.getRootElement();
                    Element ele = root.element("package-scan");
                    String res = ele.attributeValue("component-scan");
                    return res;
                }catch(Exception e){
                    e.printStackTrace();
                }
                return null;
            }
    
        }
    
    容器的实现,我们在 IOC 容器启动时,也做了 MVC 注解的扫描,将路径和方法存入 
    Map 中,并且启动了 Netty 服务器:
    
        public abstract class BeanFactory {
    
            public Object getBean(String beanName){
                return doGetBean(beanName);
            }
    
            //交给子类,容器实现类去完成
            protected abstract Object doGetBean(String beanName);
    
        }
    
        public abstract class ApplicationContext extends BeanFactory {
    
            protected String configuration;
            protected XmlUtil xmlUtil;
    
            public ApplicationContext(String configuration){
                this.configuration = configuration;
                this.xmlUtil = new XmlUtil();
            }
        }
    
    
        public class AnnotationApplicationContext extends ApplicationContext {
    
            //保存类路径的缓存
            private List<String> classCache = Collections.synchronizedList(new ArrayList<String>());
    
            //保存需要注入的类的缓存
            private List<Class<?>> beanDefinition = Collections.synchronizedList(new ArrayList<Class<?>>());
    
            //保存类实例的容器
            public Map<String,Object> beanFactory = new ConcurrentHashMap<>();
    
            // 完整路径和 方法的 mapping
            public Map<String,Object> handleMapping = new ConcurrentHashMap<>();
    
            // 类路径和controller 的 mapping
            public Map<String,Object> controllerMapping = new ConcurrentHashMap<>();
    
            public AnnotationApplicationContext(String configuration) {
                super(configuration);
                String path  = xmlUtil.handlerXMLForScanPackage(configuration);
    
                //执行包的扫描操作
                scanPackage(path);
                //注册bean
                registerBean();
                //把对象创建出来,忽略依赖关系
                doCreateBean();
                //执行容器管理实例对象运行期间的依赖装配
                diBean();
                //mvc 相关注解扫描
                mappingMVC();
                //启动 netty 服务器
                NettyHttpServer instance = NettyHttpServer.getInstance();
                try {
                    instance.start(this);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            /*
             * MVC 注解和路径扫描
             */
            private void mappingMVC() {
                //上一步已经完成了 Controller,service,respostry,autowired 等注解的扫描和注入
                //遍历容器,将 requestmapping 注解的路径和对应的方法以及 contoller 实例对应起来
                for(Map.Entry<String,Object> entry:beanFactory.entrySet()){
                    Object instance = entry.getValue();
                    Class<?> clazz = instance.getClass();
                    if(clazz.isAnnotationPresent(Controller.class)){
                        RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                        String classPath = requestMapping.value();
                        Method[] methods = clazz.getMethods();
                        for(Method method:methods){
                            if(method.isAnnotationPresent(RequestMapping.class)){
                                RequestMapping requestMapping2 = method.getAnnotation(RequestMapping.class);
                                String methodPath = requestMapping2.value();
                                String requestPath = classPath + methodPath;
                                handleMapping.put(requestPath,method);
                                controllerMapping.put(requestPath,instance);
                            }else{
                                continue;
                            }
    
                        }
                    }else{
                        continue;
                    }
                }
            }
    
    
            @Override
            protected Object doGetBean(String beanName) {
                return beanFactory.get(beanName);
            }
    
            /**
             * 扫描包下面所有的 .class 文件的类路径到上面的List中
             *
             */
            private void scanPackage(final String path) {
                System.out.println("path:"+path);
                URL url = this.getClass().getClassLoader().getResource(path.replaceAll("\.", "/"));
                System.out.println("scanPackage:" + url.getPath());
                try {
                    File file = new File(url.toURI());
    
                    file.listFiles(new FileFilter(){
                        //pathname 表示当前目录下的所有文件
                        @Override
                        public boolean accept(File pathname) {
                            //递归查找文件
                            if(pathname.isDirectory()){
                                scanPackage(path+"."+pathname.getName());
                            }else{
                                if(pathname.getName().endsWith(".class")){
                                    String classPath = path + "." + pathname.getName().replace(".class","");
                                    System.out.println("addClassPath:" +classPath );
                                    classCache.add(classPath);
                                }
                            }
                            return true;
                        }
    
                    });
                } catch (URISyntaxException e) {
                    e.printStackTrace();
                }
    
    
            }
    
    
            /**
             * 根据类路径获得 class 对象
             */
            private void registerBean() {
                if(classCache.isEmpty()){
                    return;
                }
    
    
                for(String path:classCache){
                    try {
                        //使用反射,通过类路径获取class 对象
                        Class<?> clazz = Class.forName(path);
                        //找出需要被容器管理的类,比如,@Component,@org.test.demo.Controller,@org.test.demo.Service,@Repository
                        if(clazz.isAnnotationPresent(Repository.class)||clazz.isAnnotationPresent(Service.class)
                                ||clazz.isAnnotationPresent(Controller.class)|| clazz.isAnnotationPresent(Component.class)){
                            beanDefinition.add(clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
    
            }
    
    
            /**
             *
             * 根据类对象,创建实例
             */
            private void doCreateBean() {
                if(beanDefinition.isEmpty()){
                    return;
                }
    
    
                for(Class clazz:beanDefinition){
                    try {
                        Object instance = clazz.newInstance();
                        //将首字母小写的类名作为默认的 bean 的名字
                        String aliasName = lowerClass(clazz.getSimpleName());
                        //先判断@ 注解里面是否给了 Bean 名字,有的话,这个就作为 Bean 的名字
                        if(clazz.isAnnotationPresent(Repository.class)){
                            Repository repository = (Repository) clazz.getAnnotation(Repository.class);
                            if(!"".equals(repository.value())){
                                aliasName = repository.value();
                            }
                        }
                        if(clazz.isAnnotationPresent(Service.class)){
                            Service service = (Service) clazz.getAnnotation(Service.class);
                            if(!"".equals(service.value())){
                                aliasName = service.value();
                            }
                        }
                        if(clazz.isAnnotationPresent(Controller.class)){
                            Controller controller = (Controller) clazz.getAnnotation(Controller.class);
                            if(!"".equals(controller.value())){
                                aliasName = controller.value();
                            }
                        }
                        if(clazz.isAnnotationPresent(Component.class)){
                            Component component = (Component) clazz.getAnnotation(Component.class);
                            if(!"".equals(component.value())){
                                aliasName = component.value();
                            }
                        }
                        if(beanFactory.get(aliasName)== null){
                            beanFactory.put(aliasName, instance);
                        }
    
                        //判断当前类是否实现了接口
                        Class<?>[] interfaces = clazz.getInterfaces();
                        if(interfaces == null){
                            continue;
                        }
                        //把当前接口的路径作为key存储到容器中
                        for(Class<?> interf:interfaces){
                            beanFactory.put(interf.getName(), instance);
                        }
    
    
    
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
    
    
                for (Map.Entry<String, Object> entry : beanFactory.entrySet()) {
                    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
                }
    
    
            }
    
            /**
             * 对创建好的对象进行依赖注入
             */
            private void diBean() {
                if(beanFactory.isEmpty()){
                    return;
                }
    
                for(Class<?> clazz:beanDefinition){
                    String aliasName = lowerClass(clazz.getSimpleName());
                    //先判断@ 注解里面是否给了 Bean 名字,有的话,这个就作为 Bean 的名字
                    if(clazz.isAnnotationPresent(Repository.class)){
                        Repository repository = clazz.getAnnotation(Repository.class);
                        if(!"".equals(repository.value())){
                            aliasName = repository.value();
                        }
                    }
                    if(clazz.isAnnotationPresent(Service.class)){
                        Service service = clazz.getAnnotation(Service.class);
                        if(!"".equals(service.value())){
                            aliasName = service.value();
                        }
                    }
                    if(clazz.isAnnotationPresent(Controller.class)){
                        Controller controller = clazz.getAnnotation(Controller.class);
                        if(!"".equals(controller.value())){
                            aliasName = controller.value();
                        }
                    }
                    if(clazz.isAnnotationPresent(Component.class)){
                        Component component = clazz.getAnnotation(Component.class);
                        if(!"".equals(component.value())){
                            aliasName = component.value();
                        }
                    }
    
                    //根据别名获取到被装配的 bean 的实例
                    Object instance = beanFactory.get(aliasName);
                    try{
                        //从类中获取参数,判断是否有 @Autowired 注解
                        Field[] fields = clazz.getDeclaredFields();
                        for(Field f:fields){
                            if(f.isAnnotationPresent(Autowired.class)){
                                //开启字段的访问权限
                                f.setAccessible(true);
                                Autowired autoWired = f.getAnnotation(Autowired.class);
                                if(!"".equals(autoWired.value())){
                                    //注解里写了别名
                                    f.set(instance, beanFactory.get(autoWired.value()));
    
                                }else{
                                    //按类型名称
                                    String fieldName = f.getType().getName();
                                    f.set(instance, beanFactory.get(fieldName));
                                }
                            }
                        }
                    }catch(Exception e){
                        e.printStackTrace();
                    }
    
                }
    
            }
    
    
            private String lowerClass(String simpleName) {
                char[] chars = simpleName.toCharArray();
                chars[0] += 32;
                String res = String.valueOf(chars);
                return res;
            }
    
    
        }
    
    核心路由控制 handler:
    
        public class DispatcherHandler extends SimpleChannelInboundHandler {
    
    
            private static final String CONNECTION_KEEP_ALIVE = "keep-alive";
            private static final String CONNECTION_CLOSE = "close";
            private AnnotationApplicationContext annotationApplicationContext;
            private FullHttpRequest request;
            private FullHttpResponse response;
            private Channel channel;
    
            public DispatcherHandler(AnnotationApplicationContext annotationApplicationContext){
                this.annotationApplicationContext = annotationApplicationContext;
            }
    
            @Override
            protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
                if(o instanceof FullHttpRequest) {
                    channel = channelHandlerContext.channel();
                    request = (FullHttpRequest) o;
                    String uri = request.uri();   //   /paul-mvc/com.paul.controller/method-com.paul.controller
                    System.out.println("uri: " + uri);
                    if(uri.contains("?")){
                        int index = uri.indexOf("?");
                        uri = uri.substring(0,index);
                    }
                    Method m = (Method) annotationApplicationContext.handleMapping.get(uri);
                    if (null == m) {
                        response = com.paul.http.HttpUtil.getNotFoundResponse();
                        writeResponse(true);
                        return;
                    }
                    //从容器里拿到controller 实例
                    Object instance = annotationApplicationContext.controllerMapping.get(uri);
                    Object[] args = handle(request, response, m);
                    for (Object a : args) {
                        System.out.println("Object:" + a);
                    }
                    try {
                        response = (FullHttpResponse) m.invoke(instance, args);
                        writeResponse(false);
                    } catch (Exception e) {
                        e.printStackTrace();
                        response = HttpUtil.getErroResponse();
                        writeResponse(true);
                    }
                }
    
            }
    
    
            private static Object[] handle(FullHttpRequest req, FullHttpResponse resp,Method method) throws IOException, IllegalAccessException, InstantiationException {
                Map<String, List<String>> parameters = RequestParseUtil.getParamMap(req);
                //拿到当前执行的方法有哪些参数
                Class<?>[] paramClazzs = method.getParameterTypes();
                //根据参数的个数,new 一个参数的数据
                Object[] args = new Object[paramClazzs.length];
    
                int args_i = 0;
                int index = 0;
                for(Class<?> paramClazz:paramClazzs){
                    if(FullHttpRequest.class.isAssignableFrom(paramClazz)){
                        args[args_i++] = req;
                    }
                    if(FullHttpResponse.class.isAssignableFrom(paramClazz)){
                        args[args_i++] = resp;
                    }
    
                    //判断requestParam  注解
                    Annotation[] paramAns = method.getParameterAnnotations()[index];
                    if(paramAns.length > 0){
                        for(Annotation paramAn:paramAns){
                            if(RequestParam.class.isAssignableFrom(paramAn.getClass())){
                                RequestParam rp = (RequestParam) paramAn;
                                args[args_i++] = RequestParseUtil.getParamValue(parameters, paramClazz, rp, method, index);
                            }
                        }
                    }
                    index ++;
                }
    
    
                return  args;
            }
    
            private void writeResponse(boolean forceClose){
                boolean close = isClose();
                if(!close && !forceClose){
                    response.headers().add("Content-Length", String.valueOf(response.content().readableBytes()));
                }
                ChannelFuture future = channel.writeAndFlush(response);
                if(close || forceClose){
                    future.addListener(ChannelFutureListener.CLOSE);
                }
            }
            private boolean isClose(){
                if(request.headers().contains("Connection", CONNECTION_CLOSE, true) ||
                        (request.protocolVersion().equals(HttpVersion.HTTP_1_0) &&
                                !request.headers().contains("Connection", CONNECTION_KEEP_ALIVE, true)))
                    return true;
                return false;
            }
        }
    

    这样我们就通过 Netty Http 实现了一个 MVC 框架,当然这个框架还有待改进的地方。

    目前方法参数与 request 匹配时必须使用 RequestParam 注解。
    对于没有实现接口的类如果注入时,@Autowired 注解必须指定实例名称。 以上两个问题因为目前无法获取参数名(不是参数类型),有兴趣的可以一起来改进。 最后给出源码地址:源码
    编程和码字不易,如果您觉得学到了东西,请帮忙点一下推荐或者在 github 加个 start。

  • 相关阅读:
    Docker部署LAMP项目
    Linux学习4-部署LAMP项目
    Docker环境安装
    Python数据库读写
    Python读取和写入Excel文件数据
    Python读取和写入txt,csv文件数据
    Linux学习1-软件测试从业者的高频Linux命令
    Docker的基础命令
    TurtleBot3 MODEL的相应说明
    ROS2 SLAM(同时定位和地图绘制)
  • 原文地址:https://www.cnblogs.com/paulwang92115/p/11317837.html
Copyright © 2011-2022 走看看