使用Java元注解和反射实现简单MVC框架
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应该被载入的顺序.
- 当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet
- 当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载
- 正数的值越小,该servlet的优先级越高,应用启动时就越先加载
- 当值相同时,容器就会自己选择顺序来加载
initParams:初始化参数,此处表示定义了一个名为base-package,值为com.kinson.myspring的WebInitParam对象,可以通过ServletConfig的getInitParameter("base-package");方法获取对应的值。
2、实现
2.1 工程目录结构
相关代码说明:
在 annotation 包下,我将提供自定义的注解,为了方便理解,会与 Spring MVC 保持一致。JDK 元注解介绍
为了模拟 Spring MVC 的方法调用链,我这里提供 Controller/Service/Dao 层进行测试。
提供自定义的 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框架的更多相关文章
- Java基于注解和反射导入导出Excel
代码地址如下:http://www.demodashi.com/demo/11995.html 1. 构建项目 使用Spring Boot快速构建一个Web工程,并导入与操作Excel相关的POI包以 ...
- Java之注解与反射
Java之注解与反射 注解(Annotation)简介 注解(Annotation)是从JDK5.0引入的新技术 Annotation作用:注解(Annotation)可以被其他程序如编译器等读取 A ...
- 简单mvc框架核心笔记
简单mvc框架核心笔记 看了thinkphp5的源码,模仿写了一个简单的框架,有一些心得笔记,记录一下 1.目录结构 比较简单,没有tp那么复杂,只是把需要的核心类写了一些. 核心类库放在mykj里, ...
- Java利用自定义注解、反射实现简单BaseDao
在常见的ORM框架中,大都提供了使用注解方式来实现entity与数据库的映射,这里简单地使用自定义注解与反射来生成可执行的sql语句. 这是整体的目录结构,本来是为复习注解建立的项目^.^ 好的,首先 ...
- Java元注解—— @Retention @Target @Document @Inherited
java中元注解有四个: @Retention @Target @Document @Inherited: @Retention:注解的保留位置 @Retention(RetentionPolicy. ...
- java自定义注解与反射
java注解与反射一.Java中提供了四种元注解,专门负责注解其他的注解,分别如下 1.@Retention元注解,表示需要在什么级别保存该注释信息(生命周期).可选的RetentionPoicy参数 ...
- Java基础--注解、反射
一.注解(Annotation) 1.什么是注解? 从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在编译.类加载.运行时被读取,并执行 ...
- Java元注解@Retention规则
@Retention是java当中的一个元注解,该元注解通常都是用于对软件的测试 1.适用方式: @Retention(RetentionPolicy.RUNTIME) @interf ...
- Java 元注解
元注解@Target,@Retention,@Documented,@Inherited * * @Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括: * Elemen ...
随机推荐
- .NET Core ASP.NET Core Basic 1-2 控制反转与依赖注入
.NET Core ASP.NET Core Basic 1-2 本节内容为控制反转与依赖注入 简介 控制反转IOC 这个内容事实上在我们的C#高级篇就已经有所讲解,控制反转是一种设计模式,你可以这样 ...
- 第二篇 特征点匹配以及openvslam中的相关实现详解
配置文件 在进入正题之前先做一些铺垫,在openvslam中,配置文件是必须要正确的以.yaml格式提供,通常需要指明使用的相机模型,ORB特征检测参数,跟踪参数等. #==============# ...
- 页面单击按钮弹出modaldialog然后调用ajax处理程序获取数据,给父级页面控件赋值
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="RefTopicList.asp ...
- C#开发BIMFACE系列18 服务端API之获取模型数据3:获取构件属性
系列目录 [已更新最新开发文章,点击查看详细] 本篇主要介绍如何获取单文件/模型下单个构建的属性信息. 请求地址:GET https://api.bimface.com/data/v2/fil ...
- gym/101955/problem/E - The Kouga Ninja Scrolls 线段数 维护 切比雪夫距离 2018沈阳icpc
传送门 思路: 这道题要把给定的每个坐标利用切比雪夫坐标表示,这样两个点的距离就是max(dx,dy),而不是一开始的dx + dy,有利于线段树的维护,又由于询问的是区间的最大差值,限制是两个点是属 ...
- UVA - 10480 Sabotage 最小割,输出割法
UVA - 10480 Sabotage 题意:现在有n个城市,m条路,现在要把整个图分成2部分,编号1,2的城市分成在一部分中,拆开每条路都需要花费,现在问达成目标的花费最少要隔开那几条路. 题解: ...
- FZU oj Problem 2082 过路费
Problem 2082 过路费 Pro ...
- lightoj 1074 - Extended Traffic(spfa+负环判断)
题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1074 题意:有n个城市,每一个城市有一个拥挤度ai,从一个城市I到另一个城市J ...
- 编写一个函数来找出所有不带歧义的函数名,也就是 那些只在一个模块里出现过的函数名(erlang)
erlang程序设计第八章练习题第二题: code:all_loaded()命令会返回一个由{Mod,File}对构成的列表,内含所有Erlang系统 载入的模块.使用内置函数Mod:module_i ...
- 什么是Json,Json如何使用
JavaScript Object Notation:javascript的对象表示法. 这是一种能传递对象的语法,可以是键值对,数组,以及其他对象. 轻量级的数据传输方法. json格式: { ke ...