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。

使用 Netty 实现一个 MVC 框架的更多相关文章

  1. 老司机教你用原生JDK 撸一个 MVC 框架!!!

    其实 Spring MVC 是一个基于请求驱动的 Web 框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器进行处理,具体工作原理见下图. 在这里,就不详细谈相关的原 ...

  2. 2015年3月26日 - Javascript MVC 框架DerbyJS DerbyJS 是一个 MVC 框架,帮助编写实时,交互的应用。

    2015年3月26日 -  Javascript MVC 框架DerbyJS DerbyJS 是一个 MVC 框架,帮助编写实时,交互的应用.

  3. 一个 MVC 框架以 MVVM 之「魂」复活了!

    GitHub: https://github.com/houfeng/mokit Mokit 最初编写于 2012 年,是一个面向移动应用的前端 mvc 框架,v3 版本进行了大量的重构或重写,并尽可 ...

  4. springMVC和struts2有什么不同?为什么要用springMVC或者struts2?让你实现一个MVC框架大概如何设计?

    [问题一:不同] (1)框架机制 1.Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用S ...

  5. AsMVC:一个简单的MVC框架的Java实现

    当初看了<从零开始写一个Java Web框架>,也跟着写了一遍,但当时学艺不精,真正进脑子里的并不是很多,作者将依赖注入框架和MVC框架写在一起也给我造成了不小的困扰.最近刚好看了一遍sp ...

  6. 自己写一个java的mvc框架吧(五)

    自己写一个mvc框架吧(五) 给框架添加注解的支持 一段废话 上一章本来是说这一章要写视图处理的部分,但是由于我在测试代码的时候需要频繁的修改配置文件,太麻烦了.所以这一章先把支持注解的功能加上,这样 ...

  7. 自己写一个java的mvc框架吧(四)

    自己写一个mvc框架吧(四) 写一个请求的入口,以及初始化框架 上一章写了获取方法的入参,并根据入参的参数类型进行数据转换.这时候,我们已经具备了通过反射调用方法的一切必要条件.现在我们缺少一个htt ...

  8. 自己写一个java的mvc框架吧(三)

    自己写一个mvc框架吧(三) 根据Method获取参数并转换参数类型 上一篇我们将url与Method的映射创建完毕,并成功的将映射关系创建起来了.这一篇我们将根据Method的入参参数名称.参数类型 ...

  9. 自己写一个java的mvc框架吧(二)

    自己写一个mvc框架吧(二) 自己写代码的习惯 写一个框架吧,如果这个框架会用到一些配置上的东西,我自己习惯是先不用考虑这个配置文件应该是怎样的,什么形式的,先用一个java对象(比如叫 Config ...

随机推荐

  1. Ruby中的各种比较方式对比

    Ruby中设计了很多种比较方式,所有对象都能进行==.!=.===.<=>.eql?.equal?这几种比较.此外,当实现了<=>之后,如果还include了Comparabl ...

  2. HTML5新增的表单验证功能

    一.HTML5表单的特点: HTML5 表单增加了许多内置的控件和控件属性 XHTML 中需要放在 form 之中的诸如 input/button/select/textarea 等标签元素,在 HT ...

  3. JVM中ClassLoader的学习

    JVM中class loaderの学习 一..class文件和jvm的关系 类的加载 所有的编译生成的.class文件都会被直接加载到JVM里面来吗(并不 首先我们明确一个概念,.class文件加载到 ...

  4. Java中的反射(1)

    Reflection in Java 反射到底是什么呢,我被问到的时候其实也没办法很好的回答这个问题,翻一翻博客,然后逐条讲解.今天干脆就整合一下,免得以后还要去翻. 首先讲一下Java是如何在运行时 ...

  5. php设计模式-责任链模式

    责任链模式更像是一种简化多种场景下调用处理的一种设计模式,特别适合if-else分支判断很多的场景.比如是根据不同会员等级给予不同的优惠力度. 它的定义:对象的调用是由下家的应用连接起来的处理链.一直 ...

  6. Linux嵌入式kgdb调试环境搭建

    ======================= 我的环境 ==========================PC 端: win7 + vmware-15 ubuntu16.04开发板:Freesca ...

  7. Java设计模式学习笔记(一) 设计模式概述

    前言 大约在一年前学习过一段时间的设计模式,但是当时自己的学习方式比较低效,也没有深刻的去理解.运用所学的知识. 所以现在准备系统的再重新学习一遍,写一个关于设计模式的系列博客. 废话不多说,正文开始 ...

  8. MySQL数据库设计与开发规范

    目录 1. 规范背景与目的 2. 设计规范 2.1. 数据库设计 2.1.1. 库名 2.1.2. 表结构 2.1.3. 列数据类型优化 2.1.4. 索引设计 2.1.5. 分库分表.分区表 2.1 ...

  9. Oracle Goldengate是如何保证数据有序和确保数据不丢失的?

    工作中一直在用Oracle 的中间件Oracle GondenGate 是如何保证消息的有序和不丢失呢? Oracle GoldenGate逻辑架构 首先,先看一下Oracle GoldenGate ...

  10. .Net 通过设置Access-Control-Allow-Origin来实现跨域访问

    目录 # 前言 # 为每个API接口单独添加响应头 1.针对 ASP.NET MVC 项目的Controllers 2.针对 ASP.NET Web API项目的Controllers 3.针对ASP ...