欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springmvc原理详解(手写springmvc)

程序员文章站 2022-07-01 16:15:06
最近在复习框架 在快看小说网搜了写资料 和原理 今天总结一下 希望能加深点映像 不足之处请大家指出 我就不画流程图了 直接通过代码来了解springmvc的运行机制和原理 回想用springmvc用到最多的是什么?当然是controller和RequestMapping注解啦 首先我们来看怎样定义注 ......

最近在复习框架 在搜了写资料 和原理 今天总结一下 希望能加深点映像  不足之处请大家指出

我就不画流程图了 直接通过代码来了解springmvc的运行机制和原理

回想用springmvc用到最多的是什么?当然是controller和requestmapping注解啦 

首先我们来看怎样定义注解的

首先来定义@controller

@target表示该注解运行在什么地方
1、public static final elementtypetype      类、接口(包括注释类型)或枚举声明
2、public static final elementtypefield     字段声明(包括枚举常量)
3、public static final elementtypemethod    方法声明
4、public static final elementtypeparameter     参数声明
5、public static final elementtypeconstructor    构造方法声明
6、public static final elementtypelocal_variable     局部变量声明
7、public static final elementtypeannotation_type     注释类型声明
8、public static final elementtypepackage    包声明

 

@retention :用来说明该注解类的生命周期。它有以下三个参数:


     retentionpolicy.source  : 注解只保留在源文件中


     retentionpolicy.class  : 注解保留在class文件中,在加载到jvm虚拟机时丢弃


     retentionpolicy.runtime  : 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。

 

@target(elementtype.type)//表示注解运行在哪里
@retention(retentionpolicy.runtime)//用来说明该注解类的生命周期
public @interface controller {
}
 

 

其次定义@requestmapping

 

@target({elementtype.method,elementtype.type})
@retention(retentionpolicy.runtime)
public @interface requestmapping {
public string value();//定义一个字符数组来接收路径值
}

定义好了注解 怎样让其产生效果呢?

 

假设程序启动运行中 我们如何去知道类上面是否存在注解了? 这就需要用到java的反射机制了。在运行状态中对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,我们都可以调用它任意的一个方法。

项目目录结构如下:


 

比如说在运行状态中 我们知道springmvc扫描的包为com.czmec.controller.indexcontroller这个包下面  我们通过反射就可以获取改包下的信息

 

public class test {
public static void main(string[] args) {
class clazz= indexcontroller.class;
//判断这个类是否存在@controller 是否标记controller注解
if (clazz.isannotationpresent(controller.class)){
system.out.println(clazz.getname()+",被标记为控制器!");
//吧标记了@controller注解的类管理起来
string path="";
//判断标记了controller类上是否存在@requestmapper
if (clazz.isannotationpresent(requestmapping.class)){
//如果存在 就获取注解上的路径值
requestmapping reqanno= (requestmapping) clazz.getannotation(requestmapping.class);
path=reqanno.value();
}

//获取类上路径过后 再获取该类的所有公开方法进行遍历() 并且判断哪些方法上有@requestmapping
method[] ms=clazz.getmethods();
for (method method:ms){
//如果不存在requestmapping注解 进入下一轮循环
if(!method.isannotationpresent(requestmapping.class)){
continue;
}
system.out.println("映射对外路径"+path+method.getannotation(requestmapping.class).value());
}
}
}
}
 

运行结果如下:

 

通过以上测试类 反射机制去获取加了注解的信息(个人理解:注解是一种标记 在代码做标记 然后在特定的管理下 可以通过某种动态方式找到想要的信息)

开发人员通过自己的业务需求去添加注解类 那么框架的设计就应该去吧这些添加了注解的类管理起来。

比如在springmvc配置中需要配置扫描的包 那么我们需要写一个工具类来根据配置的扫描包来管理需要管理的类

 

import java.io.file;
import java.io.ioexception;
import java.net.url;
import java.util.*;
/**
* 用来扫描指定的包下面的类
*/
public class classscanner {
/**
* 用来扫描指定的包下面的类
* @param basepackg 基础包
* @return map<string,class<?>> map<类,类的class实例>
*/
public static map<string,class<?>> scannerclass(string basepackg){
map<string,class<?>> results=new hashmap<string, class<?>>();
//com.czmec.controller
//通过包名替换成 com/czmec/controller
string filepath=basepackg.replace(".","/");
//通过类加载器获取完整路径
try {
enumeration<url> dirs=thread.currentthread().getcontextclassloader().getresources(filepath);
string rootpath=thread.currentthread().getcontextclassloader().getresource(filepath).getpath();
system.out.println(rootpath);
if (rootpath!=null){
rootpath=rootpath.substring(rootpath.lastindexof(filepath));
}
///c:/users/user/desktop/%e5%ad%a6%e4%b9%a0/springmvc/out/production/springmvc/com/czmec/controller
//获取的是类的真实的物理路径 接下来就是io啦
while (dirs.hasmoreelements()){
url url=dirs.nextelement();
system.out.println(url);
/**
* /c:/users/user/desktop/%e5%ad%a6%e4%b9%a0/springmvc/out/production/springmvc/com/czmec/controller
file:/c:/users/user/desktop/%e5%ad%a6%e4%b9%a0/springmvc/out/production/springmvc/com/czmec/controller
*/
//根据url 判断是文件还是文件夹
if (url.getprotocol().equals("file")){
file file=new file(url.getpath().substring(1));//因为路径前面多了一个/所有从1开始
//如果是文件夹 就需要递归找下去 找到所有文件
try {
scannerfile(file,rootpath,results);
} catch (classnotfoundexception e) {
e.printstacktrace();
}

}
}
} catch (ioexception e) {
e.printstacktrace();
}

return results;
}

//递归
private static void scannerfile(file folder,string rootpath,map<string,class<?>> classes) throws classnotfoundexception {
//拿到folder里面的所有文件对象 如果文件夹为空 会返回一个null
file[] files=folder.listfiles();
for (int i=0;files!=null&&i<files.length;i++){
file file=files[i];
//如果是文件夹就继续进行递归
if (file.isdirectory()){
if (rootpath.substring(rootpath.length()-1).equals("/")){
rootpath=rootpath.substring(0,rootpath.length()-1);
}
scannerfile(file,rootpath+"/"+file.getname()+"/",classes);
}else {
string path=file.getabsolutepath();//获取真实路径
if (path.endswith(".class")){
//将路径中的\替换成/
path=path.replace("\\","/");
//获取完整的类路径 比如com.czmec.indexcontroller
if (!rootpath.substring(rootpath.length()-1).equals("/")){
rootpath+="/";
}
string classname=rootpath+path.substring(path.lastindexof("/")+1,path.indexof(".class"));
classname=classname.replace("/",".");
system.out.println(classname);
//吧类路径是实例保存起来
classes.put(classname,class.forname(classname));
}
}
}
}

public static void main(string[] args) {
classscanner.scannerclass("com.czmec");
}
}
 

 

 

 

 

通过上面代码 可以看出在指定的包下 获取包下类的路径 并获取实例 保存在map中,  吧这些类扫描出来通过反射技术获取注解值,获取控制器类等等

 

在springmvc中 我们会配置springmvc的核心控制器dispatcherservlet来设置路径权限

dispatcherservlet的生命周期有:init(),service,destory;

下面是生命周期图


 

通过这个图我们可以了解到dispatcherservlet在运行过程中所起到的作用

配置dispatcherservlet有两种方式 一种是基于xml 一种是基于注解 下面我们来看看基于注解

在dispatcherservlet类中 通过设置@webservlet来设置拦截路径 @webinitparam来设置设置的包

在初始化方法中去获取包下面所有的类并且迭代所有类的实例 获取有controller注解 并通过类加载机制实例化对象

保存在map中 再获取类中所有加了@requestmapping注解的方法,和路径保存在methods中  并在service中解析请求路径通过invoke方法

 

@webservlet(urlpatterns = {"*.do"},initparams = {@webinitparam(name = "basepackage",value = "com.czmec")})
public class dispacherservlet extends javax.servlet.http.httpservlet {
// protected void dopost(javax.servlet.http.httpservletrequest request, javax.servlet.http.httpservletresponse response) throws javax.servlet.servletexception, ioexception {
//
// }
//
// protected void doget(javax.servlet.http.httpservletrequest request, javax.servlet.http.httpservletresponse response) throws javax.servlet.servletexception, ioexception {
//
// }
//存储controller实例
private map<string,object> controllers=new hashmap<string,object>();
//被反射调用的method
private map<string,method> methods=new hashmap<string,method>();
public dispacherservlet(){
super();
}

@override
public void init(servletconfig config) throws servletexception {
//获取包名
string basepackage=config.getinitparameter("basepackage");
//扫描
map<string ,class<?>> cons= classscanner.scannerclass(basepackage);
//迭代
iterator<string> itro=cons.keyset().iterator();
while (itro.hasnext()){
string classname=itro.next();
class clazz=cons.get(classname);
if (clazz.isannotationpresent(controller.class)){
system.out.println(clazz.getname()+",被标记为控制器!");
//吧标记了@controller注解的类管理起来
string path="";
//判断标记了controller类上是否存在@requestmapper
if (clazz.isannotationpresent(requestmapping.class)){
//如果存在 就获取注解上的路径值
requestmapping reqanno= (requestmapping) clazz.getannotation(requestmapping.class);
path=reqanno.value();
}
try {
controllers.put(classname,clazz.newinstance());
} catch (instantiationexception e) {
e.printstacktrace();
} catch (illegalaccessexception e) {
e.printstacktrace();
}
//获取类上路径过后 再获取该类的所有公开方法进行遍历 并且判断哪些方法上有@requestmapping
method[] ms=clazz.getmethods();
for (method method:ms){
//如果不存在requestmapping注解 进入下一轮循环
if(!method.isannotationpresent(requestmapping.class)){
continue;
}
system.out.println("映射对外路径"+path+method.getannotation(requestmapping.class).value());
methods.put(path+method.getannotation(requestmapping.class).value(),method);
}
}
}
}

@override
protected void service(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception {
system.out.println(req.getrequesturi());
///abc.do
string uri=req.getrequesturi();
string contextpath=req.getcontextpath();
//获取映射路径
string mappingpath=uri.substring(uri.indexof(contextpath)+contextpath.length(),uri.indexof(".do"));
//
method method=methods.get(mappingpath);
//获取实例对象
object controller=controllers.get(method.getdeclaringclass().getname());
try {
method.invoke(controller);
} catch (illegalaccessexception e) {
e.printstacktrace();
} catch (invocationtargetexception e) {
e.printstacktrace();
}
}
}
 

 

配上tomcat运行

输入地址:http://localhost:8080/czmec/delete.do


 

通过以上代码的说明 可以了解基本springmvc的运行机制和原理

至此最基本的springmvc就完成了

有哪里错误的地方请指出 

源代码下载地址:https://github.com/dcg123/springmvc