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. Oracle数据库测试和优化最佳实践: OTest介绍 (转)

    当前Oracle数据库最佳测试工具OTest *  Otest是用于Oracle数据库测试.优化.监控软件. *  Otest是免费提供给Oracle客户和广大DBA工程师使用的软件.由原厂技术专家王 ...

  2. MySQL运行时自动生成的性能相关的数据参考

      某大师曾说过,一个DBA要像熟悉自己的老婆一样熟悉自己的数据库,个人认为包含了两个方面的熟悉: 1,在稳定性层面来说,更多的是关注高可用.读写分离.负载均衡,灾备管理等等high level层面的 ...

  3. window下 局域网内使用mysql,mysql 开启远程访问权限

    一.window 10 开启3306端口 1.按win键选择设置 2.搜索防火墙 3.选择高级设置 3.右键入站规则->新建规则 4.按照提示,规则类型选择端口,应用于tcp,特定本地端口输入3 ...

  4. 边缘缓存模式(Cache-Aside Pattern)

    边缘缓存模式(Cache-Aside Pattern),即按需将数据从数据存储加载到缓存中.此模式最大的作用就是提高性能减少不必要的查询. 1 模式 先从缓存查询数据 如果没有命中缓存则从数据存储查询 ...

  5. 自己实现vue瀑布流组件,含详细注释

    我知道vue有瀑布流插件vue-waterfall-easy,但是使用的时候与我的预期有部分别,所以就自己动手写了这个组件 人和动物的根本区别是是否会使用工具,我们不仅要会使用,还要会创造工具,别人提 ...

  6. Unity3D 基于ShadowMap的平滑硬阴影

    前言 传统的ShadowMap在明暗边缘处都会有很难看的锯齿,因此一般得到的结果会比较难看,常规的解决办法都会在使用ShadowMap渲染阴影的时候通过背面剔除把这种缺陷隐藏掉,最后剩下一个影子.但是 ...

  7. Mybatis案例升级版——小案例大道理

    纯Mybatis案例升级版——小案例大道理 前言: 这几天看了一本书<原则>,在上面看到了一句话叫“每个人都把自己眼界的局限当成世界的局限”,大学生是

  8. hdu-6601 Keen On Everything But Triangle

    题目连接: http://acm.hdu.edu.cn/showproblem.php?pid=6601 Description N sticks are arranged in a row, and ...

  9. codeforces 361 D. Levko and Array(dp+二分)

    题目链接:http://codeforces.com/contest/361/problem/D 题意:最多可以修改K次数字,每次修改一个数字变成任意值,C=max(a[i+1]-a[i]):求操作之 ...

  10. react-router url参数更新 但是页面不更新的解决办法

    今天发现, 当使用react-router(v4.2.2)时,路由需要传入参数, 但是如果路由跳转时,url仅仅改变的是参数部分,如从hello/1跳转到hello/2,此时虽然参数更新了,但是页面是 ...