Springmvc的核心是DispatcherServlet来进行各种请求的拦截,进而进行后续的各种转发处理。流程图如下:

  说明:客户端发出一个http请求给web服务器,web服务器对http请求进行解析,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),web容器将请求转交给DispatcherServlet.DipatcherServlet接收到这个请求之后将根据请求的信息(包括URL、Http方法、请求报文头和请求参数Cookie等)以及HandlerMapping的配置找到处理请求的处理器(Handler)。DispatcherServlet根据HandlerMapping找到对应的Handler,将处理权交给Handler(Handler将具体的处理进行封装),再由具体的HandlerAdapter对Handler进行具体的调用。Handler对数据处理完成以后将返回一个ModelAndView()对象给DispatcherServlet。Handler返回的ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet通过 ViewResolver将逻辑视图转化为真正的视图View。Dispatcher通过model解析出ModelAndView()中的参数进行解析最终展现出完整的view并返回给客户端。

以下基于java元注解和反射等知识来实现简单MVC框架。

1、Servlet

Servlet 3.0 之前使用web.xml文件进行配置,例如:

  <servlet>
<serlvet-name>myServlet</servlet-name>
<servlet-calss>MyServlet的类路径</servlet-class>
</servlet> <servlet-mapping>
  <serlvet-name>myServlet</servlet-name>
  <url-pattern>/servlet/myServlet</url-pattern>
</servlet-mapping>

Servlet 3.0 后可以基于注解来处理Servlet

@WebServlet(name = "dispatcherServlet", urlPatterns = "/*", loadOnStartup = 1,
initParams = {@WebInitParam(name = "base-package", value = "com.kinson.myspring")})

name:servlet名

urlPatterns:url匹配模式

loadOnStartup:标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法),它的值必须是一个整数,表示servlet应该被载入的顺序.

  1.   当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet
  2.   当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载
  3.   正数的值越小,该servlet的优先级越高,应用启动时就越先加载
  4.   当值相同时,容器就会自己选择顺序来加载

initParams:初始化参数,此处表示定义了一个名为base-package,值为com.kinson.myspring的WebInitParam对象,可以通过ServletConfig的getInitParameter("base-package");方法获取对应的值。

2、实现

2.1 工程目录结构

相关代码说明:

  1. 在 annotation 包下,我将提供自定义的注解,为了方便理解,会与 Spring MVC 保持一致。JDK 元注解介绍

  2. 为了模拟 Spring MVC 的方法调用链,我这里提供 Controller/Service/Dao 层进行测试。

  3. 提供自定义的 DispatcherServlet 来完成核心逻辑处理。

具体代码实现:

2.2 pom引入servlet依赖

    <!--项目依赖-->
<dependencies>
<!-- servlet 依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
</dependency> </dependencies>

2.3 Annotation

以Controller为例,其他的注解类似:

//用于类、接口、枚举enum
@Target(ElementType.TYPE)
//生命周期为运行时
@Retention(RetentionPolicy.RUNTIME)
//javadoc
@Documented
public @interface Controller { /**
* 作用于该注解的一个value属性
* @return
*/
String value();
}

2.4 请求拦截类DispatcherServlet :

定义相关全局存储变量:

// @WebServlet 以前我们定义一个 Servlet ,需要在 web.xml 中去配置,不过在 Servlet 3.0 后出现了基于注解的 Servlet 。
@WebServlet(name = "dispatcherServlet", urlPatterns = "/*", loadOnStartup = 1,
initParams = {@WebInitParam(name = "base-package", value = "com.kinson.myspring")})
public class DispatcherServlet extends HttpServlet {
/**
* 扫描的包
*/
private String basePackage = ""; /**
* 基包下面所有的带包路径权限定类名
*/
private List<String> packageNames = new ArrayList<String>(); /**
* 注解实例化 格式为注解上的名称:注解实例化对象
*/
private Map<String, Object> instanceMap = new HashMap<String, Object>(); /**
* 包路径权限定类名称:注解上的名称
*/
private Map<String, String> nameMap = new HashMap<String, String>(); /**
* Url地址和方法的映射关系:注解上的名称
*/
private Map<String, Method> urlMethodMap = new HashMap<String, Method>(); /**
* Method和权限定类名的映射关系,用于通过Method找到该方法的对象利用反射执行
*/
private Map<Method, String> methodPackageMap = new HashMap<Method, String>();
}

初始化方法init:

  /**
* 初始化
* 1、扫描基包下的类,得到信息 A。
* 2、对于 @Controller/@Service/@Repository 注解而言,我们需要拿到对应的名称,并初始化它们修饰的类,形成映射关系 B。
* 3、扫描类中的字段,如果发现有 @Qualifier 的话,我们需要完成注入。
* 4、扫描 @RequestMapping,完成 URL 到某一个 Controller 的某一个方法上的映射关系 C。
*
* @param config
*/
@Override
public void init(ServletConfig config) {
System.out.println("开始初始化。。。。。。"); //通过初始化参数直接将需要扫描的基包路径传入
basePackage = config.getInitParameter("base-package"); try {
//扫描基包得到全部的带包路径权限定类名
scanBasePackage(basePackage);
//把代用注解的类实例化方如Map中,key为注解上的名称
instance(packageNames);
//IOC注入
springIOC();
//完成Url地址与方法的映射关系
handleUrlMethodMap();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} System.out.println("初始化结束。。。。。。");
}

方法scanBasePackage通过初始化参数直接将需要扫描的基包路径传入:

  /**
* 通过初始化参数直接将需要扫描的基包路径传入
*
* @param basePackage 基包路径
*/
private void scanBasePackage(String basePackage) {
//加载类资源路径
URL url = this.getClass().getClassLoader()
.getResource(basePackage.replaceAll("\\.", "/")); File basePackageFile = new File(url.getPath());
System.out.println("scan:" + basePackageFile);
File[] childFiles = basePackageFile.listFiles();
for (File file : childFiles) {
//目录递归扫描
if (file.isDirectory()) {
scanBasePackage(basePackage + "." + file.getName());
} else if (file.isFile()) {
//Controller.class====Controller,即去掉.class
System.out.println(">>>>>>>>>>> " + file.getName() + "====" + file.getName().split("\\.")[0]);
packageNames.add(basePackage + "." + file.getName().split("\\.")[0]);
}
}
}

方法instance把代用注解的类实例化方如Map中,key为注解上的名称:

/**
* 把代用注解的类实例化方如Map中,key为注解上的名称
*
* @param packageNames 包路径名集合
*/
private void instance(List<String> packageNames) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
if (packageNames.size() < 1) {
return;
} for (String packageName : packageNames) {
//根据包路径获取Class对象
Class<?> clazz = Class.forName(packageName);
//Controller注解处理
if (clazz.isAnnotationPresent(Controller.class)) {
Controller controller = (Controller) clazz.getAnnotation(Controller.class);
String controllerName = controller.value(); instanceMap.put(controllerName, clazz.newInstance());
nameMap.put(packageName, controllerName); System.out.println("Controller :" + packageName + ", value :" + controllerName);
} else if (clazz.isAnnotationPresent(Service.class)) {
//Service注解处理
Service service = (Service) clazz.getAnnotation(Service.class);
String serviceName = service.value(); instanceMap.put(serviceName, clazz.newInstance());
nameMap.put(packageName, serviceName); System.out.println("Service :" + packageName + ", value :" + serviceName);
} else if (clazz.isAnnotationPresent(Repository.class)) {
//Repository注解处理
Repository repository = clazz.getAnnotation(Repository.class);
String repositoryName = repository.value(); instanceMap.put(repositoryName, clazz.newInstance());
nameMap.put(packageName, repositoryName);
System.out.println("Repository :" + packageName + ", value :" + repositoryName);
}
}
}

方法springIOC注入:

/**
* IOC注入
*/
private void springIOC() throws IllegalAccessException {
for (Map.Entry<String, Object> instanceEntry : instanceMap.entrySet()) {
//获取当前对象的所有字段
Field[] declaredFields = instanceEntry.getValue().getClass().getDeclaredFields(); for (Field field : declaredFields) {
//字段上是否有Qualifier注解
if (field.isAnnotationPresent(Qualifier.class)) {
Qualifier qualifier = field.getAnnotation(Qualifier.class);
String qualifierName = qualifier.value(); //设置当前的字段为可访问
field.setAccessible(Boolean.TRUE);
//设置当前字段
field.set(instanceEntry.getValue(), instanceMap.get(qualifierName)); System.out.println("==========" + field);
}
}
}
}

方法handleUrlMethodMap处理Url地址与方法的映射关系:

/**
* Url地址与方法的映射关系
*
* @throws ClassNotFoundException
*/
private void handleUrlMethodMap() throws ClassNotFoundException {
if (packageNames.size() < 1) {
return;
} for (String packageName : packageNames) {
//根据包路径获取Class对象
Class clazz = Class.forName(packageName); //当前类是否有Controller注解
if (clazz.isAnnotationPresent(Controller.class)) {
//获取当前Controller类的所有方法
Method[] methods = clazz.getMethods();
//拼接访问URI
StringBuffer baseUrl = new StringBuffer(); //当前Controller是否有RequestMapping注解
if (clazz.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
//XxxController类上的requestMapping值
baseUrl.append(requestMapping.value());
} for (Method method : methods) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
//XxxController类上的响应方法上的requestMapping值
baseUrl.append(requestMapping.value()); //URL 提取出来,映射到 Controller 的 Method 上。
System.out.println("baseUrl : " + baseUrl.toString());
urlMethodMap.put(baseUrl.toString(), method);
methodPackageMap.put(method, packageName);
}
}
}
}
}

doGet/doPost方法处理拦截请求的业务逻辑:

   @Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
doPost(req, resp);
} @Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) { //获取请求URI,eg:/user/hello
final String uri = req.getRequestURI();
final String contextPath = req.getContextPath();
final String path = uri.replaceAll(contextPath, ""); //提取出 URL,通过 URL 映射到Method 上,然后通过反射的方式进行调用即可。
Method method = urlMethodMap.get(path);
if (null != method) {
//通过方法获取方法所在的包路径
String packageName = methodPackageMap.get(method);
//通过包路径获取注解上的名称
String controllerName = nameMap.get(packageName);
//通过注解名称获取对应的实例对象
UserController userController = (UserController) instanceMap.get(controllerName); try {
//设置方法可访问
method.setAccessible(Boolean.TRUE);
//利用反射进行方法调用
method.invoke(userController);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}

测试UserController类:

@Controller("userController")
@RequestMapping("/user")
public class UserController { @Qualifier("userServiceImpl")
private UserService userService; @RequestMapping(value = "/hello")
public String hello() {
System.out.println("UserController.hello");
return "UserController.hello";
}
}

测试UserService接口:

public interface UserService {

    void hello();
}

测试UserServiceImpl接口:

@Service("userServiceImpl")
public class UserServiceImpl implements UserService { @Override
public void hello() {
System.out.println("hello, myspring");
}
}

3、测试

配置tomcat运行项目,此处我用的是idea,具体配置:

选择本地安装的tomcat:

设置相关内容:

点击右下角的Fix按钮选择部署包:

配置好之后运行,在浏览器输入测试url:

idea控制台打印了内容:

到此,一个简单的MVC框架就ok了。

Github源码参照

使用Java元注解和反射实现简单MVC框架的更多相关文章

  1. Java基于注解和反射导入导出Excel

    代码地址如下:http://www.demodashi.com/demo/11995.html 1. 构建项目 使用Spring Boot快速构建一个Web工程,并导入与操作Excel相关的POI包以 ...

  2. Java之注解与反射

    Java之注解与反射 注解(Annotation)简介 注解(Annotation)是从JDK5.0引入的新技术 Annotation作用:注解(Annotation)可以被其他程序如编译器等读取 A ...

  3. 简单mvc框架核心笔记

    简单mvc框架核心笔记 看了thinkphp5的源码,模仿写了一个简单的框架,有一些心得笔记,记录一下 1.目录结构 比较简单,没有tp那么复杂,只是把需要的核心类写了一些. 核心类库放在mykj里, ...

  4. Java利用自定义注解、反射实现简单BaseDao

    在常见的ORM框架中,大都提供了使用注解方式来实现entity与数据库的映射,这里简单地使用自定义注解与反射来生成可执行的sql语句. 这是整体的目录结构,本来是为复习注解建立的项目^.^ 好的,首先 ...

  5. Java元注解—— @Retention @Target @Document @Inherited

    java中元注解有四个: @Retention @Target @Document @Inherited: @Retention:注解的保留位置 @Retention(RetentionPolicy. ...

  6. java自定义注解与反射

    java注解与反射一.Java中提供了四种元注解,专门负责注解其他的注解,分别如下 1.@Retention元注解,表示需要在什么级别保存该注释信息(生命周期).可选的RetentionPoicy参数 ...

  7. Java基础--注解、反射

    一.注解(Annotation) 1.什么是注解? 从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在编译.类加载.运行时被读取,并执行 ...

  8. Java元注解@Retention规则

    @Retention是java当中的一个元注解,该元注解通常都是用于对软件的测试 1.适用方式:     @Retention(RetentionPolicy.RUNTIME)     @interf ...

  9. Java 元注解

    元注解@Target,@Retention,@Documented,@Inherited * * @Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括: * Elemen ...

随机推荐

  1. CSS 之Grid 网格知识梳理2

    继上篇的CSS 之Grid下半部分   14.将单元格划分到一个区域,使用grid-template-areas属性: ag: grid-template-areas: "header  h ...

  2. vscode同步插件 sync(gist,token)

    网上很多同步教程,按照教程操作upload时一直报错:sync:invalid gist ID 查找问题很久才知道 gist和token是两个东西.下面重新梳理下: 一.下载安装插件 Setting ...

  3. BS/CS 区别

    转载自:https://www.cnblogs.com/chenmingjun 一.CS和BS含义: CS即Client/Server(客户机/服务器)结构.C/S结构在技术上很成熟,它的主要特点是交 ...

  4. Installing the JMeter CA certificate for HTTPS recording

    参考: http://jmeter.apache.org/usermanual/component_reference.html#HTTP(S)_Test_Script_Recorder User m ...

  5. 51 nod 石子归并 + v2 + v3(区间dp,区间dp+平行四边形优化,GarsiaWachs算法)

    题意:就是求石子归并. 题解:当范围在100左右是可以之间简单的区间dp,如果范围在1000左右就要考虑用平行四边形优化. 就是多加一个p[i][j]表示在i到j内的取最优解的位置k,注意能使用平行四 ...

  6. 51nod 1020 逆序排列(dp,递推)

    题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1020 题意:是中文题. 题解:很显然要设dp[i][j]表示 ...

  7. java学习笔记之基础篇

    java选择语句之switch   //switch可以用于等值判断 switch (e) //int ,或则可以自动转化成int 的类型,(byte char short)枚举jdk 7中可以防止字 ...

  8. 007 Python程序语法元素分析

    目录 一.概述 二.程序的格式框架 2.1 代码高亮 2.2 缩进 2.3 注释 2.4 缩进.注释 三.命名与保留字 3.1 变量 3.2 命名 3.3 保留字 3.4 变量.命名.保留字 四.数据 ...

  9. d010:盈数、亏数和完全数

    题目: 对一个正整数N而言,将它除了本身以外所有的因子加起来的总和为S,如果S>N,则N为盈数,如果S<N,则N为亏数,而如果S=N,则N为完全数(Perfect Number).例如10 ...

  10. 使用JAVA API获取hadoop集群的FileSystem

    所需要配置的参数:  Configuration conf = new Configuration();   conf.set("fs.defaultFS", "hdfs ...