手写SpringMVC框架
前言
本篇内容为图灵学院VIP上课笔记,补充了老师留的注入参数,分别是直接注入,@RequestParam
,@RequestBody
三种方式。
需要JAVA基础知识扎实,熟练使用反射,自定义注解等。
项目的所有代码我就不贴了,要全部代码的可以私聊我。比较重要的地方我会贴上。
项目结构图
pom文件及说明
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baiqi</groupId>
<artifactId>baiqi_mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>baiqi_mvc Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
最下面的plugin的parameters是为了解决反射获取参数是arg0用的。需要在idea的setting里进行设置
流程图
创建控制层、业务层代码(Controller、Service、准备自定义注 解)、准备SpringMvc核心配置文件
自定义注解
比如AutoWired注解
/*
@Retention注解表示Annotation的保留策略
RetentionPolicy.Class:运行时不保留,不可以通过反射读取。
RetentionPolicy.RUNTIME:运行是保留,可以通过反射读取。
RetentionPolicy.SOURCE:丢弃。
*
*/
@Target(value= ElementType.FIELD)
@Retention(value= RetentionPolicy.RUNTIME)
public @interface AutoWired {
String value();
}
自定义注解很基本我就不多说了。不会的看B站狂神说的课程。
前端控制器
准备前端控制器 ,创建一个Servlet,同时在web.xml文件中声明该前端控制器
创建Spring容器,通过DOM4J解析springmvc的XML文件
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置前端控制器-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>com.springmvc.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--Web服务器一旦启动,Servlet就会实例化创建对象,然后初始化(预备创建对象)-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
前端控制器
创建Spring容器,通过DOM4J解析springmvc的XML文件
扫描springmvc中的控制器以及service类并实例化对象放入容器中
SpringMVC容器
WebApplicationContext
package com.springmvc.context;
import com.springmvc.annotation.AutoWired;
import com.springmvc.annotation.Controller;
import com.springmvc.annotation.Service;
import com.springmvc.xml.XmlPaser;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author 白起老师
* springMvc容器
*/
public class WebApplicationContext {
//classpath:springmvc.xml
String contextConfigLocation;
//定义集合 用于存放 bean 的权限名|包名.类名
List<String> classNameList = new ArrayList<String>();
//创建Map集合用于扮演IOC容器: key存放bean的名字 value存放bean实例
public Map<String,Object> iocMap = new ConcurrentHashMap<>();
public WebApplicationContext() {
}
public WebApplicationContext(String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
/**
* 初始化Spring容器
*/
public void onRefresh(){
//1、进行解析springmvc配置文件操作 ==》 com.baiqi.controller,com.baiqi.service
String pack = XmlPaser.getbasePackage(contextConfigLocation.split(":")[1]);
String[] packs = pack.split(",");
//2、进行包扫描
for(String pa : packs){
excuteScanPackage(pa);
}
//3、实例化容器中bean
executeInstance();
//4、进行 自动注入操作
executeAutoWired();
}
//进行自动注入操作
public void executeAutoWired(){
try {
//从容器中 取出 bean ,然后判断 bean中是否有属性上使用 AutoWired,如果使用了搞注解,就需要进行自动注入操作
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
//获取容器中的bean
Object bean = entry.getValue();
//获取bean中的属性
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if(field.isAnnotationPresent(AutoWired.class)){
//获取注解中的value值|该值就是bean的name
AutoWired autoWiredAno = field.getAnnotation(AutoWired.class);
String beanName = autoWiredAno.value();
//取消检查机制
field.setAccessible(true);
field.set(bean,iocMap.get(beanName));
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
/**
* 实例化容器中的bean
*/
public void executeInstance(){
try{
// com.baiqi.controller.UserController com.baiqi.service.impl.UserServiceImpl
for (String className : classNameList) {
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(Controller.class)){
//控制层 bean
String beanName = clazz.getSimpleName().substring(0,1).toLowerCase()+ clazz.getSimpleName().substring(1);
iocMap.put(beanName,clazz.newInstance());
}else if(clazz.isAnnotationPresent(Service.class)){
//Service层 bean
Service serviceAn = clazz.getAnnotation(Service.class);
String beanName = serviceAn.value();
iocMap.put(beanName,clazz.newInstance());
}
}
}catch(Exception e){
e.printStackTrace();
}
}
/**
* 扫描包
*/
public void excuteScanPackage(String pack){
// com.baiqi.controller ==> com/baiqi/controller
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));
String path = url.getFile();
// /com/bruce/service
File dir=new File(path);
for(File f:dir.listFiles()){
if(f.isDirectory()){
//当前是一个文件目录 com.baiqi.service.impl
excuteScanPackage(pack+"."+f.getName());
}else{
//文件目录下文件 获取全路径 UserController.class ==> com.baiqi.controller.UserController
String className=pack+"."+f.getName().replaceAll(".class","");
classNameList.add(className);
}
}
}
}
实现容器中对象的注入,比如将服务层对象注入至控制层
建立请求映射地址与控制器以及方法之间的映射关系
package com.springmvc.servlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springmvc.annotation.*;
import com.springmvc.context.WebApplicationContext;
import com.springmvc.handler.MyHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author tianzhen
*/
public class DispatcherServlet extends HttpServlet {
//指定SpringMvc容器
private WebApplicationContext webApplicationContext;
/***
* 创建集合 用于存放 映射关系 映射地址 与 控制器.方法,用于发送请求直接从该集合中进行匹配
*/
List<MyHandler> handList = new ArrayList<>();
@Override
public void init() throws ServletException {
//1、加载初始化参数 classpath:springmvc.xml
String contextConfigLocation = this.getServletConfig().getInitParameter("contextConfigLocation");
//2、创建Springmvc容器
webApplicationContext = new WebApplicationContext(contextConfigLocation);
//3、进行初始化操作
webApplicationContext.onRefresh();
//4、初始化请求映射关系 /findUser ===》控制器.方法
initHandlerMapping();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//进行请求分发处理
doDispatcher(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
/***
* 进行请求分发处理
* @param req
* @return
*/
public void doDispatcher(HttpServletRequest req, HttpServletResponse resp){
//根据用户的请求地址 /findUser 查找Handler|Controller
MyHandler myHandler = getHandler(req);
try{
if(myHandler == null){
resp.getWriter().print("<h1>404 NOT FOUND!</h1>");
}else{
//调用处理方法之前 进行参数的注入
List<Object> args=new ArrayList<>();
//当有RequestParam注解时
Method method = myHandler.getMethod();
Class<?> clazz = method.getParameterTypes()[0];
Object o = clazz.newInstance();
for (Parameter parameter : method.getParameters()) {
if (parameter.isAnnotationPresent(RequestParam.class)) {
String param=parameter.getAnnotation(RequestParam.class).value();
args.add(req.getParameter(param));
}else if(parameter.isAnnotationPresent(RequestBody.class)){
//从request输入流里获取json字符串
BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream()));
StringBuffer buffer = new StringBuffer();
String line = " ";
while ((line = reader.readLine()) != null){
buffer.append(line);
}
ObjectMapper objectMapper = new ObjectMapper();
//json串转为map
Map<String,Object> map = objectMapper.readValue(buffer.toString(), Map.class);
for (String str : map.keySet()) {
System.out.println(str);
//获取所有字段数组,对参数对象进行实例化
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (str.equals(field.getName())) {
field.set(o, map.get(str));
}
}
}
args.add(o);
}else {
//当无注解,直接注入时,方法2
args.add(req.getParameter(parameter.getName()));
}
}
//调用目标方法
Object result = myHandler.getMethod().invoke(myHandler.getController(),args.toArray());
if(result instanceof String){
//跳转JSP
String viewName=(String)result;
// forward:/success.jsp
if(viewName.contains(":")){
String viewType=viewName.split(":")[0];
String viewPage=viewName.split(":")[1];
if(viewType.equals("forward")){
req.getRequestDispatcher(viewPage).forward(req,resp);
}else{
// redirect:/user.jsp
resp.sendRedirect(viewPage);
}
}else{
//默认就转发
req.getRequestDispatcher(viewName).forward(req,resp);
}
}else{
//返回JSON格式数据
if(method.isAnnotationPresent(ResponseBody.class)){
//将返回值转换成 json格式数据
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(result);
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
writer.print(json);
writer.flush();
writer.close();
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
/***
* 获取请求对应的handler,根据用户请求查找对应的Handler
* @param req
* @return
*/
public MyHandler getHandler(HttpServletRequest req) {
// /findUser
String requestURI = req.getRequestURI();
for (MyHandler myHandler : handList) {
//从容器的Handle取出URL 和 用户的请求地址进行匹配,找到满足条件的Handler
if (myHandler.getUrl().equals(requestURI)) {
return myHandler;
}
}
return null;
}
/**
* 初始化请求映射关系
*/
public void initHandlerMapping(){
for (Map.Entry<String, Object> entry : webApplicationContext.iocMap.entrySet()) {
//获取bean的class类型
Class<?> clazz = entry.getValue().getClass();
if(clazz.isAnnotationPresent(Controller.class)){
//获取bean中所有的方法,为这些方法建立映射关系
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if(method.isAnnotationPresent(RequestMapping.class)){
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
//获取注解中的值 /findUser
String url = requestMapping.value();
//建立 映射地址 与 控制器.方法
MyHandler myHandler = new MyHandler(url,entry.getValue(),method);
handList.add(myHandler);
}
}
}
}
}
}
封装控制器
package com.springmvc.handler;
import java.lang.reflect.Method;
/**
* @BelongsProject: SpringMvc
* @Description: TODO
*/
public class MyHandler {
//请求URL地址
private String url;
//后台控制器
private Object controller;
//控制器中指定的方法
private Method method;
public MyHandler() {
super();
}
public MyHandler(String url, Object controller, Method method) {
super();
this.url = url;
this.controller = controller;
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
}