1、SpringMVC的理解
1)谈谈对Spring MVC的了解
MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
-
Model:数据模型,JavaBean的类,用来进行数据封装。
-
View:指JSP、HTML用来展示数据给用户
-
Controller:用来接收用户的请求,整个流程的控制器。用来进行数据校验等
2)Spring MVC的核心组件
DispatcherServlet
:核心的中央处理器,负责接收请求、分发,并给予客户端响应。
HandlerMapping
:处理器映射器,根据 URL 去匹配查找能处理的 Handler
,并会将请求涉及到的拦截器和 Handler
一起封装。
HandlerAdapter
:处理器适配器,根据 HandlerMapping
找到的 Handler
,适配执行对应的 Handler
;
Handler
:请求处理器,处理实际请求的处理器。
ViewResolver
:视图解析器,根据 Handler
返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet
响应客户端
3)Spring MVC的工作流程
Spring MVC的工作流程如下:
-
用户发送请求至前端控制器DispatcherServlet
-
DispatcherServlet收到请求调用处理器映射器HandlerMapping。
-
处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
-
DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
-
执行处理器Handler(Controller,也叫页面控制器)。
-
Handler执行完成返回ModelAndView
-
HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
-
DispatcherServlet将ModelAndView传给ViewReslover视图解析器
-
ViewReslover解析后返回具体View
-
DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
-
DispatcherServlet响应用户。
2、代码实现
1)测试代码
import com.heaboy.mvc.XxhhMvc;public class Main {static {String path = Main.class.getResource("").getPath();String packageName = Main.class.getPackage().getName();XxhhMvc.scanner(path,packageName);}public static void main(String[] args) {XxhhMvc.exec("","");XxhhMvc.exec("test","index1");XxhhMvc.exec("test","");XxhhMvc.exec("test","asdfasdfasdf");XxhhMvc.exec("test","");}
}
输出结果:
index -> index
test->index1
test->index
没有这个方法 404
test->index
2)定义注解
import java.lang.annotation.*;/*** controller声明*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @ interface Controller {
}
@Documented
:- 这个元注解表明
@Controller
注解应该被javadoc或类似的工具记录。也就是说,当你在编写Java文档时,@Controller
注解的信息会被包含在生成的文档中。这对于理解代码中的注解用途非常有帮助。
- 这个元注解表明
@Retention(RetentionPolicy.RUNTIME)
:- 这个元注解指定了
@Controller
注解的保留策略。RetentionPolicy.RUNTIME
意味着这个注解在运行时仍然保留,因此它可以通过反射(Reflection)被读取。这对于那些需要在运行时通过注解来获取信息或行为的框架(如Spring MVC)来说是非常重要的。
- 这个元注解指定了
@Target(ElementType.TYPE)
:- 这个元注解指定了
@Controller
注解可以应用的Java元素类型。ElementType.TYPE
表明这个注解只能用于类、接口(包括注解类型)或枚举声明上。这意味着你不能将这个注解用于方法、字段等其他元素上。
- 这个元注解指定了
public @interface Controller
:- 这行声明了
@Controller
是一个注解(Annotation),并且它是公开的(public),意味着它可以被任何其他类访问。@interface
关键字用于声明注解类型,与声明接口(interface)类似,但注解(Annotation)不包含方法实现。
- 这行声明了
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {/**** @return*/String value() default "";
}
@Documented
:- 如前所述,这个元注解表明
@RequestMapping
注解应该被 javadoc 或类似的工具记录。这有助于在生成的文档中包含注解的信息,从而帮助开发者理解代码中的注解用途。
- 如前所述,这个元注解表明
@Retention(RetentionPolicy.RUNTIME)
:- 这个元注解指定了
@RequestMapping
注解的保留策略为RUNTIME
,意味着这个注解在运行时仍然保留,因此它可以通过反射被读取。这对于 Spring MVC 框架在运行时解析注解信息并映射请求到相应的处理器方法至关重要。
- 这个元注解指定了
@Target({ElementType.TYPE, ElementType.METHOD})
:- 这个元注解指定了
@RequestMapping
注解可以应用的 Java 元素类型。在这个例子中,它指定了注解可以应用于类(TYPE
)和方法(METHOD
)上。这允许开发者在类级别上定义基础的请求路径,然后在方法级别上进一步细化这个路径。
- 这个元注解指定了
public @interface RequestMapping
:- 这行声明了
@RequestMapping
是一个公开的注解类型。
- 这行声明了
String value() default "";
:- 这是
@RequestMapping
注解的一个属性(也称为元素)。它定义了请求的 URL 路径模式。value
是这个属性的名称,而default ""
表示如果在使用注解时没有指定value
属性,它将默认为空字符串。但是,在 Spring MVC 中,更常见的做法是使用path
属性(尽管value
和path
在@RequestMapping
中是等价的,可以互换使用)。
- 这是
3)SpringMVC核心类
第一步:扫描并注册MVC组件
static {String path = Main.class.getResource("").getPath();String packageName = Main.class.getPackage().getName();SpringMvc.scanner(path,packageName);}
首先获取当前类的路径和包名,然后根据当前类路径和包名进行SpringMvc组件扫描
SpringMvc首先定义两个全局HashMap如下:
private static HashMap<String, Map<String,Method>> map=new HashMap<>();
//用来存储类上的@RequestMapping的值->(方法上的@RequestMapping的值->对应的方法)private static HashMap<String, Object> objMap=new HashMap<>();
//用来存储类上的@RequestMapping的值->对应类的实例
SpringMvc类的scanner方法如下:
public static void scanner(String path,String packageName){List<String> paths = traverseFolder2(path);for (String p : paths) {p=p.substring(path.length()-1); //得到文件名try {String className=packageName+"."+p.replaceAll( Matcher.quoteReplacement(File.separator),"."); //得到包名加文件名String replace = className.replace(".class", ""); //得到去掉.class后缀的包全限定名Class<?> cl = ClassLoader.getSystemClassLoader().loadClass(replace); //获取类的class对象if(isController(cl)){if(isRequestMapping(cl)){RequestMapping requestMapping = getRequestMapping(cl);if(map.containsKey(requestMapping.value())){throw new RuntimeException("类多注解值:"+requestMapping.value());}else {map.put(requestMapping.value(),new HashMap<>());objMap.put(requestMapping.value(),cl.newInstance());}Method[] declaredMethods = cl.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {if(isRequestMapping(declaredMethod)){RequestMapping mapping = getRequestMapping(declaredMethod);if(map.get(requestMapping.value()).containsKey(mapping.value())){throw new RuntimeException("方法多注解值:"+requestMapping.value());}else {map.get(requestMapping.value()).put(mapping.value(),declaredMethod);}}}}else {throw new RuntimeException("类无requestMapping");}}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}}
首先根据路径遍历文件夹,找到所有的class文件,将类文件路径列表放到paths中
private static List<String> traverseFolder2(String path) {File file = new File(path);List<String> classFiles=new ArrayList<>();if (file.exists()) {LinkedList<File> list = new LinkedList<File>();File[] files = file.listFiles();for (File file2 : files) {if (file2.isDirectory()) {list.add(file2);} else {classFiles.add(file2.getAbsolutePath());}}File temp_file;while (!list.isEmpty()) {temp_file = list.removeFirst();files = temp_file.listFiles();for (File file2 : files) {if (file2.isDirectory()) {list.add(file2);} else {classFiles.add(file2.getAbsolutePath());}}}} else {}return classFiles;}
然后处理每个类文件路径:对于paths
列表中的每个路径p
,代码执行以下操作:
-
提取文件名,得到xxx.class
-
构造全限定类名:通过将包名
packageName
、文件分隔符替换为点(.
),以及去除.class
后缀,来构造类的全限定名。 -
加载类:使用
ClassLoader.getSystemClassLoader().loadClass(replace);
加载类。 -
检查是否为控制器:
isController(cl)
方法检查该类是否是一个控制器(即是否有@Controller注解)。
private static boolean isController(Class cl){Annotation annotation = cl.getAnnotation(Controller.class);if(annotation!=null){return true;}return false;}
-
检查类上是否有
@RequestMapping
注解:isRequestMapping(cl)
方法。
private static boolean isRequestMapping(Class cl){Annotation annotation = cl.getAnnotation(RequestMapping.class);if(annotation!=null){return true;}return false;}
-
处理类上的
@RequestMapping
:如果类上有@RequestMapping
注解,则提取其值(通常是URL路径模式),并检查是否已经在映射中注册了相同的值。如果没有,则将该类的实例(通过cl.newInstance()
)和该类的方法映射添加到相应的 objMap映射中。 -
处理类中的方法:遍历类的所有声明的方法,检查它们是否也有
@RequestMapping
注解。如果有,则将这些方法映射到它们各自的URL路径上,在map中存储。
private static boolean isRequestMapping(Method method){Annotation annotation = method.getAnnotation(RequestMapping.class);if(annotation!=null){return true;}return false;}
-
异常处理:如果在处理过程中发现任何问题(如类加载失败、类没有
@RequestMapping
注解、或者同一个URL路径被多个类或方法注册),则抛出RuntimeException
。
第二步:使用类路径和方法路径找到对应的controller执行方法
public static void exec(String classPath,String methodPath){if(objMap.get(classPath)==null){System.out.println("没有这个类 404");}else {if(map.get(classPath).get(methodPath)==null){System.out.println("没有这个方法 404");}else {try {map.get(classPath).get(methodPath).invoke(objMap.get(classPath));} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}}
由于在第一步已经将带有@Controller注解的类中带有@RequestMapping注解的类和其方法都存储在objMap和map中,这一步直接在其寻找,如存在该映射路径,则直接利用反射调用对应方法执行
本案例提供了两个controller,如下
import com.xxhh.annotation.Controller;
import com.xxhh.annotation.RequestMapping;@Controller
@RequestMapping("test")
public class TestController {@RequestMappingpublic String index(){System.out.println("test->index");return "";}@RequestMapping("index1")public String index1(){System.out.println("test->index1");return "";}
}
import com.xxhh.annotation.Controller;
import com.xxhh.annotation.RequestMapping;@Controller
@RequestMapping
public class IndexController {@RequestMappingpublic void index(){System.out.println("index -> index");}
}
最终执行结果如下:
index -> index
test->index1
test->index
没有这个方法 404
test->index
以上就是简单实现SpringMVC的全部源码,如有错误,欢迎指正!!!