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. [Python] Django框架入门

    说明:Django框架入门 当前项目环境:python3.5.django-1.11 项目名:test1 应用名:booktest 命令可简写为:python manager.py xxx => ...

  2. Nacos整合Spring Cloud Gateway组件

    一.什么是Spring Cloud Gateway Spring Cloud Gateway是Spring Cloud官方推出的网关框架,网关作为流量入口有着非常大的作用,常见的功能有路由转发.权限校 ...

  3. codeforce617E-XOR and Favorite Number莫队+异或前缀和

    传送门:http://codeforces.com/contest/617/problem/E 参考:https://blog.csdn.net/keyboarderqq/article/detail ...

  4. hihocoder #1617 : 方格取数(dp)

    题目链接:http://hihocoder.com/problemset/problem/1617 题解:一道递推的dp题.这题显然可以考虑两个人同时从起点出发这样就不会重复了设dp[step][i] ...

  5. Codeforces 889F Letters Removing(二分 + 线段树 || 树状数组)

    Letters Removing 题意:给你一个长度为n的字符串,然后进行m次删除操作,每次删除区间[l,r]内的某个字符,删除后并且将字符串往前补位,求删除完之后的字符串. 题解:先开80个set ...

  6. Codeforces 468 B Two Sets

    Two Sets 题意:就是将一对数放进setA, setB中, 如果放进setA的话要求满足 x与a-x都在这个集合里面, 如果放进setB中要求满足x与b-x都在这个集合中. 题解:我们将能放进B ...

  7. lightoj 1028 - Trailing Zeroes (I)(素数筛)

    We know what a base of a number is and what the properties are. For example, we use decimal number s ...

  8. hdu 3265 Posters(线段树+扫描线+面积并)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3265 题意:给你一张挖了洞的墙纸贴在墙上,问你总面积有多少. 挖了洞后其实就是多了几个矩形墙纸,一张墙 ...

  9. 洛谷 题解 P3871 【[TJOI2010]中位数】

    这题先定义一个大根堆(maxn)维护mid(n为奇数mid+1)的元素.再定义一个小根堆(minn)维护mid(n为奇数mid+1)到n的元素.然后对于插入元素的情况进行分类讨论. 当add x时 一 ...

  10. 题解 UVA11000 【Bee】

    传送门 [题目描述] 在非洲有一种非常特殊的蜜蜂.每年,这种蜜蜂的一只雌蜂生育一只 雄蜂,而一只雄蜂生育一只雌蜂和一只雄蜂,生育后它们都会死去!现在科学家们意外地发现了这一特殊物种的一只神奇的雌蜂,她 ...