手写MVC初体验(一)
这里写目录标题
一级目录
二级目录
三级目录
根据咕泡学院的课程,加入自己的理解,开始了手撸轮子的第一天。仿照spring mvc
的运行流程,实现一个简单的Spring MVC框架。
1. 知识点
spring mvc 是基于 spring 的 mvc 框架 ,这次实现初始化Ioc和DI操作、Controller层、Service层。视图处理这块内容以后再说,我们先通过responce.getWriter().write()来返回数据。
手写体验主要是为了增进对Spring和SpringMVC的理解,从中可以学习到注解的使用、反射的使用。
下边就是手写Spring MVC的整体流程
2 项目框架
文件结构如上
第一版就是先实现
- @Autowired
- @Controller
- @Service
- @RequestMapping
- @RequestParam
- DispatcherServlet
- web.xml
- application.properties
3. 注解的编写
import java.lang.annotation.*;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/6/14 22:56
*/
@Target({ElementType.FIELD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
String value() default "" ;
}
import java.lang.annotation.*;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/6/14 22:53
*/
@Target({ElementType.TYPE})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
String value() default "" ;
}
import java.lang.annotation.*;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/6/14 22:54
*/
@Target({ElementType.TYPE})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
String value() default "" ;
}
import java.lang.annotation.*;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/6/14 22:54
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD , ElementType.TYPE})
@Documented
public @interface RequestMapping {
String value() default "" ;
}
import java.lang.annotation.*;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/6/14 22:55
*/
@Target({ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestParam {
String value() default "" ;
}
其实注解就是根据Spring提供的注解来创建的,不同的一点就是@Autowired中有value,也就是既可以根据type注入,也可以根据name注入
4. web.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>GuoYHang Web Application</display-name>
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.framework.spring.mvc.servlet.v1.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfiguration</param-name>
<param-value>application.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
5. application.properties配置
scanPackage=org.framework.demo
这里只有一个包名,也就是需要扫描的包
6. DispatcherServlet编写-init初始化
1. 继承HttpServlet
import org.framework.spring.annotation.Autowired;
import org.framework.spring.annotation.Component;
import org.framework.spring.mvc.annotation.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/6/14 23:00
*/
public class DispatcherServlet extends HttpServlet {
private Properties properties = new Properties() ;
private List<String> classNameList = new ArrayList<String>();
private Map<String , Object> ioc = new HashMap<String, Object>() ;
private Map<String , Method> handlerMapping = new HashMap<String, Method>() ;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
//6.找到HandlerMapping执行方法,并执行
try {
doDispatcher(req , resp) ;
} catch (IOException e) {
e.printStackTrace();
try {
resp.getWriter().write("500");
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
@Override
public void init(ServletConfig config) throws ServletException {
//1.加载配置文件
doLoadConfig(config.getInitParameter("contextConfiguration")) ;
//2.扫描包下的类
doScanner(properties.getProperty("scanPackage")) ;
//3.完成Ioc容器
doInstance() ;
//4.DI注入
doAutowired() ;
//5.初始化HandlerMapping
doInitHandlerMapping() ;
}
}
2. 配置读取
编写doLoadConfig
方法获取web.xml中的contextConfiguration
对应的value值,也就是我们在application.properties
中写的数据。
scanPackage=org.framework.demo
//配置
private Properties properties = new Properties() ;
/**
* 加载web.xml中的配置
* @param contextConfiguration
* @throws IllegalArgumentException
*/
private void doLoadConfig(String contextConfiguration) throws IllegalArgumentException {
if ("".equals(contextConfiguration)){
throw new IllegalArgumentException("Web Application need value of contextConfiguration in the web.xml") ;
}
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfiguration) ;
try {
//加载application.properties文件
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (null != inputStream){
try {
//关闭输入流
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3. 扫描包下的类和接口
private List<String> classNameList = new ArrayList<String>();
/**
* 扫描类
* application.properties下的scanPackage属性
* @param scanPackage
*/
private void doScanner(String scanPackage) {
if (isNull(scanPackage) || !isPackageRegular(scanPackage)){
throw new IllegalArgumentException("scanPackage of application.properties is error") ;
}
URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.","/"));
//获取文件夹
File classPath = new File(url.getFile()) ;
for (File file : classPath.listFiles()) {
//是文件夹
if (file.isDirectory()) {
//递归调用
doScanner(scanPackage + "." + file.getName());
continue ;
}
//非.class文件
if (!file.getName().endsWith(".class")) {
continue;
}
classNameList.add(scanPackage + "." + file.getName().replace(".class","")) ;
}
}
4. 完成Ioc的注入
private Map<String , Object> ioc = new HashMap<String, Object>() ;
/**
* 初始化Ioc容器
*/
private void doInstance() {
if (classNameList.isEmpty()){
return ;
}
try{
for (String className : classNameList) {
//通过反射创建实例对象
Class<?> clazz = Class.forName(className) ;
//value、类名首字母小写、接口名
String beanName = "";
Object instance = null ;
if (clazz.isAnnotationPresent(Controller.class)){
//controller
beanName = toLowerFirstAlp(clazz.getSimpleName()) ;
instance = clazz.newInstance() ;
ioc.put(beanName , instance) ;
}else if (clazz.isAnnotationPresent(Service.class)){
//service
//获取注解
Service service = clazz.getAnnotation(Service.class) ;
String serviceValue ;
beanName = "".equals(serviceValue = service.value().trim()) ?
toLowerFirstAlp(clazz.getSimpleName()) : serviceValue ;
instance = clazz.newInstance() ;
ioc.put(beanName , instance) ;
//接口对应
for (Class<?> inter : clazz.getInterfaces()){
beanName = inter.getSimpleName() ;
if (ioc.containsKey(beanName)){
throw new Exception(beanName + " is exist") ;
}
ioc.put(beanName , instance) ;
}
continue ;
}
}
}catch (ClassNotFoundException e){
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 将类名的首字母小写
* @param simpleName
* @return
*/
private String toLowerFirstAlp(String simpleName) {
// TODO: 2020/6/16
char[] chars = simpleName.toCharArray() ;
chars[0] += 32 ;
String name = new String(chars) ;
return name ;
}
实际上的Ioc是Map存储的一个beanName和instance对应的列表
5. 进行依赖注入
/**
* 依赖注入
*/
private void doAutowired() {
if (ioc.isEmpty()){
return ;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
//获取所有的字段
Field[] fields = entry.getValue().getClass().getDeclaredFields() ;
for (Field field : fields) {
//不需要自动注入
if (!field.isAnnotationPresent(Autowired.class)){
continue ;
}
//自动注入
Autowired autowired = field.getAnnotation(Autowired.class) ;
String autowiredValue ;
//1.autowired -> value
//2.field -> type
String beanName = "".equals(autowiredValue = autowired.value().trim())
? toLowerFirstAlp(field.getType().getSimpleName()) : autowiredValue ;
//private域的属性开通道
field.setAccessible(true) ;
try {
field.set(entry.getValue() , ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
continue ;
}
}
}
}
依赖注入是从Ioc容器中获取instance来注入到依赖的instance中
6.初始化HandlerMapping
private Map<String , Method> handlerMapping = new HashMap<String, Method>() ;
private void doInitHandlerMapping() {
if (ioc.isEmpty()){
return ;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass() ;
if (!clazz.isAnnotationPresent(Controller.class)){
continue ;
}
//controller中标记在类上的requestMapping的value
String baseUrl = clazz.isAnnotationPresent(RequestMapping.class)
? clazz.getAnnotation(RequestMapping.class).value().trim() : "" ;
Method[] methods = clazz.getMethods() ;
for (Method method : methods) {
if (!method.isAnnotationPresent(RequestMapping.class)){
continue ;
}
String url = "/" + baseUrl + "/" + method.getAnnotation(RequestMapping.class).value() ;
url = url.replaceAll("/+" , "/") ;
handlerMapping.put(url , method) ;
}
}
}
7. DispatcherServlet编写-doPost/doGet
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
我们将doPost和doGet合并为同一个,在调用doGet时,调用doPost
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
//6.找到HandlerMapping执行方法
try {
doDispatcher(req , resp) ;
} catch (IOException e) {
e.printStackTrace();
try {
resp.getWriter().write("500");
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String requestURI = req.getRequestURI() ;
String contextPath = req.getContextPath() ;
String url = requestURI.replaceAll(contextPath , "").replaceAll("/+" , "/") ;
if (!handlerMapping.containsKey(url)){
resp.getWriter().write("404");
}
Method method = handlerMapping.get(url);
Map<String , String[]> paramMap = req.getParameterMap() ;
Class<?>[] paramTypes = method.getParameterTypes() ;
Object[] paramValues = new Object[paramTypes.length] ;
//进行参数的匹配
for (int i=0 ; i<paramTypes.length ; i++){
Class paramType = paramTypes[i] ;
if (paramType == HttpServletRequest.class){
paramValues[i] = req ;
}else if (paramType == HttpServletResponse.class){
paramValues[i] = resp ;
}else if (paramType == String.class){
//获取参数上的注解列表
Annotation[][] annotations = method.getParameterAnnotations();
//遍历第i个参数上的注解,通过注解中的value,给传参列表赋值
for (Annotation a : annotations[i]){
if ( a instanceof RequestParam){
String paramName = ((RequestParam) a).value() ;
if (!"".equals(paramName)){
String value = Arrays.toString(paramMap.get(paramName))
.replaceAll("\\[|\\]" , "")
.replaceAll("\\s",",");
paramValues[i] = value ;
}
}
}
}
}
String beanName = toLowerFirstAlp(method.getDeclaringClass().getSimpleName()) ;
try {
//invoke反射执行
method.invoke(ioc.get(beanName) , paramValues) ;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
8. 启动
添加测试
import org.framework.demo.service.DemoService;
import org.framework.spring.annotation.Autowired;
import org.framework.spring.mvc.annotation.Controller;
import org.framework.spring.mvc.annotation.RequestMapping;
import org.framework.spring.mvc.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/6/17 9:49
*/
@Controller
@RequestMapping(value = "/demo")
public class DemoController {
@Autowired
private DemoService demoService ;
@RequestMapping(value = "/say")
public void say(HttpServletRequest request , HttpServletResponse response , @RequestParam("name")String name){
System.out.println(name);
String result = demoService.process(name) ;
try {
response.getWriter().write("<h1>"+result+"</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
import org.framework.spring.mvc.annotation.Service;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/6/17 23:21
*/
@Service
public class DemoService {
public String process(String name) {
return "service : i am "+ name +"" ;
}
}
首先要install项目,然后配置tomcat
http://localhost:8888/demo/say?name=Java
response.getWriter().write("<h1>"+result+"</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
```java
import org.framework.spring.mvc.annotation.Service;
/**
* @author gyh
* @csdn https://blog.csdn.net/qq_40788718
* @date 2020/6/17 23:21
*/
@Service
public class DemoService {
public String process(String name) {
return "service : i am "+ name +"" ;
}
}
首先要install项目,然后配置tomcat
http://localhost:8888/demo/say?name=Java
[外链图片转存中…(img-rHxkYjJr-1592666642228)]