手写springmvc的思路
springmvc的核心也是一个servlet即DispatcherServlet,这个servlet会配在web.xml里面,注意它的url-pattern配置的是/*,即拦截了所有请求。
其原理就是当浏览器发送请求的时候,都会被springmvc的这个servlet拦截到,然后springmvc会将这个请求的url进行一些处理,处理后去匹配对应的controller中对应的method(requestMaping中填写的请求路径)。匹配之后就通过反射调用方法,完成业务逻辑。
按照这个思路我们开始编写一个简单的springmvc框架:
1、框架的包结构
mvc包是springmvc的框架包,包括annotation、applicationContext、config、servlet、util包
web是应用包,包括controller、service
1、编写web.xml
web.xml主要是配置我们的servlet,其中param-value代表配置文件的名称。
<?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">
<servlet>
<servlet-name>SyxDispatcherServlet</servlet-name>
<servlet-class>syx.mvc.servlet.SyxDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SyxDispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
2、编写一个servlet继承HttpServlet,重写里面的init、dopost、doget方法,注意init方法很重要,用于我们初始化spring的上下文。
package syx.mvc.servlet;
import syx.mvc.applicationContext.ApplicationContext;
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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
public class SyxDispatcherServlet extends HttpServlet {
//配置文件的key
private static final String CONFIGLOCATION = "contextConfigLocation";
//spring上下文
ApplicationContext ctx;
/**
* 初始化加载配置文件
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) {
//获取servlet的初始化参数
String configLocation = config.getInitParameter(CONFIGLOCATION);
//初始化spring上下文
ctx = new ApplicationContext(configLocation);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
this.doPost(req,resp);
}
//处理业务逻辑
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
String URL = req.getRequestURL().toString();
//对URL进行截取
String convertUrl = convert(URL);
ConcurrentHashMap<String, HashMap<String, Method>> methodMapping = ctx.getMethodMapping();
methodMapping.forEach((beanName,map)->{
//如果匹配的上就反射调用方法(参数列表还未完善)
if(map.containsKey(convertUrl)){
try {
map.get(convertUrl).invoke(ctx.getBean(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
//TODO 对URL进行转换
private String convert(String url) {
return url;
}
}
init方法中首先获得了web.xml中配置文件的名称(配置文件说明了包扫描路径,即IOC容器中的bean),然后使用配置文件初始化spring应用上下文。
3、最重要的一个类ApplicationContext
applicationcontext类干的事:
1、加载配置文件
2、根据配置文件里配置的扫描路径扫描所有的class文件,生成beanDefinitionMap即,bean的定义集合。
3、根据beanDefinitionMap初始化方法和url映射关系的集合。
4、包含一个getBean和createBean的方法,即根据beanDefinitionMap创建bean然后存放到单例池中。
package syx.mvc.applicationContext;
import syx.mvc.annotation.Autowired;
import syx.mvc.config.BeanDefinition;
import syx.mvc.util.*;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* spring应用上下文
*/
public class ApplicationContext {
//bean的定义信息
private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
//单例池
private ConcurrentHashMap<String, Object> singletonBeans = new ConcurrentHashMap<>();
//方法和URL的映射map
private ConcurrentHashMap<String, HashMap<String, Method>> methodMapping = new ConcurrentHashMap<>();
public ApplicationContext() {
}
public ApplicationContext(String configLocation) {
init(configLocation);
}
/**
* 初始化spring应用上下文
*/
private void init(String configLocation) {
try {
//加载配置文件
Properties properties = PropertiesUtil.loadPropertoes(configLocation);
//根据读取的配置进行扫描,返回扫描得到的class文件
List<String> classNames = doScan(properties);
//初始化beanDefinitionMap
IOCMapUtil.IOCMap(classNames, beanDefinitionMap);
//初始化methodMapping
MethodMappingUtil.UrlMapping(beanDefinitionMap, methodMapping);
} catch (IOException e) {
e.printStackTrace();
}
}
//根据读取的配置进行扫描
private List<String> doScan(Properties properties) {
//扫描的路径
String scanPackage = PropertiesUtil.getString(properties, "scanPackage");
return LoadFileUtil.loadFiles(scanPackage);
}
/**
* 根据BeanDefinition创建bean
*
* @param v
* @return
*/
private Object createBean(BeanDefinition v) {
try {
Class clazz = v.getBeanClass();
//实例化(bean的生命周期)
Object instance = clazz.newInstance();
//填充属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) {
Object o = getBean(field.getName());
field.setAccessible(true);
field.set(instance, o);
}
}
//回掉
//初始化
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取bean
*
* @param beanName
* @return
*/
public Object getBean(String beanName) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("prototype")) {
return createBean(beanDefinition);
} else {
Object obj = singletonBeans.get(beanName);
if (obj == null) {
Object bean = createBean(beanDefinition);
singletonBeans.put(beanName, bean);
return bean;
}
return obj;
}
}
public ConcurrentHashMap<String, HashMap<String, Method>> getMethodMapping() {
return methodMapping;
}
public void print(){
methodMapping.forEach((k,v)->{
System.out.println(k);
System.out.println(v);
});
}
}
4、bean的定义类BeanDefinition
package syx.mvc.config;
/**
* bean的定义
*/
public class BeanDefinition {
//bean的名字
private String beanName;
//作用域
private String scope;
//bean的类型
private Class beanClass;
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public Class getBeanClass() {
return beanClass;
}
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
@Override
public String toString() {
return "BeanDefinition{" +
"beanName='" + beanName + '\'' +
", scope='" + scope + '\'' +
", beanClass=" + beanClass +
'}';
}
}
5、相关的注解
package syx.mvc.annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "";
}
package syx.mvc.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
String value() default "";
}
package syx.mvc.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
@Documented
public @interface Controller {
String value() default "";
}
package syx.mvc.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
/**
* 请求路径
* @return
*/
String value() default "";
/**
* 请求方法
* @return
*/
RequestMethod method() default RequestMethod.GET;
}
package syx.mvc.annotation;
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
package syx.mvc.annotation;
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
package syx.mvc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value() default "singleton";
}
package syx.mvc.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
@Documented
public @interface Service {
String value() default "";
}