springmvc原理详解(手写springmvc)
最近在复习框架 在搜了写资料 和原理 今天总结一下 希望能加深点映像 不足之处请大家指出
我就不画流程图了 直接通过代码来了解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
上一篇: 让Controller支持对平铺参数执行@Valid数据校验
下一篇: 使用Lombok总结
推荐阅读
-
SpringMvc面试专题知识点及其详解
-
详解springMVC容器加载源码分析
-
详解SpringMVC Controller介绍及常用注解
-
Spring+SpringMVC配置事务管理无效原因及解决办法详解
-
详解Spring MVC如何测试Controller(使用springmvc mock测试)
-
SpringMVC开发restful API之用户查询代码详解
-
SpringMVC拦截器实现监听session是否过期详解
-
springMVC利用FastJson接口返回json数据相关配置详解
-
详解SpringMVC Controller介绍及常用注解
-
springMVC引入Validation的具体步骤详解