paulwang92115

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 的包扫描路径。




@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 的相关功能。

服务启动类,单例:
```java
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:
```java
    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 用作参数的映射。此处代
码都一样,就不一一列举了。
```java
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}

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

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

先看 xml 解析类,将 component-scan 中的包名解析出来:
```java
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 服务器:
```java
public abstract class BeanFactory {

    public Object getBean(String beanName){
        return doGetBean(beanName);
    }

    //交给子类,容器实现类去完成
    protected abstract Object doGetBean(String beanName);

}
```
```java
public abstract class ApplicationContext extends BeanFactory {

    protected String configuration;
    protected XmlUtil xmlUtil;

    public ApplicationContext(String configuration){
        this.configuration = configuration;
        this.xmlUtil = new XmlUtil();
    }
}

```
```java
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:
```java
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。

分类:

技术点:

相关文章: