我们都知道spring的执行原理,甚至为了面试倒背如流(http请求–>dispatcherServlet–>HandlerMapping–>Handler–>…),我之前虽然是知道这些东西,但是感觉对它又很模糊,dispatcherServlet具体是什么?HandlerMapping又是干什么的?IOC、DI是怎么实现的?这些东西在脑海中都是似懂非懂的。百度查资料什么的其实都不如自己手写一遍来的实在。也就几百行代码,手写一遍,相信你一定会变得更自信。
注意:代码中注释都很全。源码地址:
https://gitee.com/isczy/mySpringCore.git
1.准备工作
配置application.properties,就和xml中配置的一个性质
配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>WebApplication</display-name>
<servlet>
<servlet-name>MySpringMvc</servlet-name>
<servlet-class>com.czy.project.springMVCframework.servlet.MyDispatcherServlet</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>MySpringMvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
2.编写自定义注解
自定义一些常用的注解:如
@Autowired
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
String value() default "";
}
@Controller
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
@RequestMapping
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value() default "";
}
@RequestParam
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value() default "";
}
@Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
3.编写简单的Controller、Service
@MyController
@MyRequestMapping("/demo")
public class DemoController {
@MyAutowired
private DemoService demoService;
@MyRequestMapping("/query")
public void query(HttpServletRequest req, HttpServletResponse resp,
@MyRequestParam("name") String name,@MyRequestParam("age") int age){
String result = demoService.query(name)+" and age is "+age;
try {
resp.setHeader("Content-type", "text/html;charset=UTF-8");
resp.setCharacterEncoding("utf-8");
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
@MyRequestMapping("/add")
public void add(HttpServletRequest req, HttpServletResponse resp,
@MyRequestParam("name") String name){
String result = demoService.add(name);
try {
resp.setHeader("Content-type", "text/html;charset=UTF-8");
resp.setCharacterEncoding("utf-8");
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
@MyRequestMapping("/remove")
public void remove(HttpServletRequest req,HttpServletResponse resp,
@MyRequestParam("name") String name){
try {
String result = demoService.remove(name);
resp.setHeader("Content-type", "text/html;charset=UTF-8");
resp.setCharacterEncoding("utf-8");
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
public interface DemoService {
String query (String name);
String add(String name);
String remove(String name);
}
@MyService
public class DemoServiceImpl implements DemoService {
@Override
public String query(String name) {
return "query success :My name is " + name ;
}
@Override
public String add(String name) {
return "添加成功:新增用户 " + name ;
}
@Override
public String remove(String name) {
return "删除成功:删除用户 " + name ;
}
}
4.编写核心重点:DispatcherServlet
/**
* 入口类
* 即spring中的DispatcherServlet
* @author czy
*/
public class MyDispatcherServlet extends HttpServlet {
private static final String LOCATION = "contextConfigLocation";//即web.xml中配置的init-param-name
private Properties properties = new Properties();//用于保存配置文件application.properties的内容
private List<String> classNames = new ArrayList<String>();//保存所有扫描出的全限定类名的集合
private Map<String,Object> ioc = new HashMap<String,Object>();//ioc容器
//保存所有的Url和方法的映射关系
private List<Handler> handlerMapping = new ArrayList<Handler>();
/**
* 初始化一系列操作
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
//1.加载配置文件
//根据contextConfigLocation名称从web.xml中获取出配置文件全称:application.properties
String initParameter = config.getInitParameter(LOCATION);
doLoadConfig(initParameter);
/**
* 2.扫描指定包下的类:spring中在application.xml中配置scanPackage指定扫描的包路径
* 这里直接用application.properties代替
*/
String scanPackage = properties.getProperty("scanPackage");//通过scanPackage键获取值,即获取要扫描的包路径
doScanner(scanPackage);
//3.初始化所有相关类的实例(这里就是初始化加@MyController,@MyService的类),并保存到IOC容器中
doInstance();
//4.依赖注入
doAutowired();
//5.构造HandlerMapping
initHandlerMapping();
//******************到此为止,spring相关ioc、di等初始化完成**********************
//******************等待请求,匹配URL,定位方法, 反射调用执行*******************
//******************调用doGet或者doPost方法*************************************
System.out.println("MyDispatcherServlet[初始化完成]。。。等待调用。。。");
}
/**
* doGet和doPost方法大家应该相当了解了
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);//doGet的操作交个doPost来执行
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try{
doDispatch(req,resp); //开始匹配到对应的方法
}catch (Exception e){
//如果匹配过程出现异常,将异常信息打印出去
resp.getWriter().write("500 Exception,Details:\r\n" + Arrays.toString(e.getStackTrace()).
replaceAll("\\[|\\]", "").
replaceAll(",\\s", "\r\n"));//将‘[’或者‘]’替换成"",将空白字符替换成换行符
}
}
/**
*匹配URL
* @param req
* @param resp
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
Handler handler = getHandler(req);
if (null == handler){
//如果没有匹配上,返回404错误
resp.getWriter().write("404 Not Found");//常见操作。。。
return;
}
System.out.println("已匹配到处理器["+handler+"]");
//获取方法的参数列表
Class<?>[] paramTypes = handler.method.getParameterTypes();
//保存所有需要自动赋值的参数值
Object [] paramValues = new Object[paramTypes.length];
Map<String,String[]> parameterMap = req.getParameterMap();//获取用户传入的参数map
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
//获取所有参数即数组,并将数组转化成string,并替换[]为""、空白字符为","
String value = new String(Arrays.toString(param.getValue()).getBytes("iso8859-1"),"utf-8").
replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
//如果handler的参数列表没有用户传入的key则跳出本次循环
if (!handler.paramIndexMapping.containsKey(param.getKey()))continue;
//找到匹配的参数,则开始填充参数值
int index = handler.paramIndexMapping.get(param.getKey());//根据参数名获取参数对应的序号
//url传过来的参数都是String类型的,HTTP是基于字符串协议
//只需要把String转换为所需类型就好
paramValues[index] = convert(paramTypes[index],value);
}
//设置方法中的request和response对象
int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
paramValues[reqIndex] = req;
int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
paramValues[respIndex] = resp;
//执行url对应的方法
System.out.println("开始执行url对应的方法:"+handler.method.getName());
handler.method.invoke(handler.controller, paramValues);
}
/**
*获取处理器Handler
* @param request
* @return
*/
private Handler getHandler(HttpServletRequest request) {
if (handlerMapping.isEmpty())return null;
String url = request.getRequestURI();
String contextPath = request.getContextPath();
url = url.replace(contextPath,"").replaceAll("/+","/");
for (Handler handler : handlerMapping) {
Matcher matcher = handler.pattern.matcher(url);//根据用户的url获取一个匹配器matcher
//matches方法用于全字符串匹配也就是100%匹配
if (!matcher.matches())continue;//如果没有匹配到则跳过本次循环
return handler;
}
return null;
}
/**
* 初始化HandlerMapping(处理器映射器):
* HandlerMapping其实就是一个包含Handler的集合
*/
private void initHandlerMapping() {
if (ioc.isEmpty())return;
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
//如果该类上没有MyController注解则跳过本次循环
if (!clazz.isAnnotationPresent(MyController.class))continue;
String url = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class)){
//如果类上有MyRequestMapping注解,获取Controller的url配置
url =clazz.getAnnotation(MyRequestMapping.class).value();
}
//获取Method的url配置
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//没有加RequestMapping注解的直接忽略
if(!method.isAnnotationPresent(MyRequestMapping.class))continue;
//映射URL
MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
String regex =("/"+url+"/"+requestMapping.value()).replaceAll("/+","/");
Pattern pattern = Pattern.compile(regex);
handlerMapping.add(new Handler(entry.getValue(),method,pattern));
System.out.println("HandlerMapping初始化完成【url:" + regex +"】" +"【method:" + method+"】");
}
}
}
/**
* 依赖注入:就是拿到ioc容器中的类,然后访问类中的字段属性,是否包含Autowired注解
* 然后从ioc容器中拿到实例并初始化该字段
*/
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) {
//如果该属性上没有MyAutowired注解则跳过本次循环
if (!field.isAnnotationPresent(MyAutowired.class))continue;
MyAutowired autowired = field.getAnnotation(MyAutowired.class);
String beanName = autowired.value();
if ("".equals(beanName)){//用户没有指定注入的beanName
beanName = field.getType().getName();//那么beanName就是该属性的名称
}
field.setAccessible(true);//暴力访问私有属性
try {
field.set(entry.getValue(),ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
continue ;
}
}
}
}
/**
* 初始化所有相关类的实例,并保存到IOC容器中
*/
private void doInstance() {
if(classNames.size() == 0)return;
//遍历全限定类名集合,拿到各类,并初始化
try {
for (String className : classNames) {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyController.class)){ //如果类上声明了MyController注解
//将类名作为beanName,即ioc容器的key
String beanName = clazz.getName();
ioc.put(clazz.getName(),clazz.newInstance());
System.out.println("["+beanName+"]已添加到IOC容器");
}else if (clazz.isAnnotationPresent(MyService.class)){//如果类上声明了MyService注解
MyService myService = clazz.getAnnotation(MyService.class);
String beanName = myService.value();//获取自定义的beanName
if (!"".equals(beanName.trim())){//如果用户设置了自定义的beanName,就用用户自己设置
ioc.put(beanName,clazz.newInstance());
System.out.println("["+beanName+"]已添加到IOC容器");
continue;
}
//如果自己没设,就按接口类型创建一个实例
Class<?>[] interfaces = clazz.getInterfaces();//获取该类实现的接口类
for (Class<?> c : interfaces) {
ioc.put(c.getName(),clazz.newInstance());
System.out.println("["+c.getName()+"]已添加到IOC容器");
}
}else{
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 扫描指定包下的类
* @param scanPackage:包扫描路径
*/
private void doScanner(String scanPackage) {
//将包路径转换为文件路径:即将com.czy.project.demo转化为 com/czy/project/demo
String s = scanPackage.replace(".", "/");
URL url = this.getClass().getClassLoader().getResource(s);
File dir = new File(url.getFile());//获取该路径的文件
//遍历该文件夹下的所有文件
for (File file : dir.listFiles()) {
if (file.isDirectory()){
doScanner(scanPackage+"."+file.getName());//如果该文件是个文件夹,继续递归
}else {
//将全限定类名添加到集合
classNames.add(scanPackage+"."+file.getName().replace(".class","").trim());
}
}
for (String className : classNames) {
System.out.println("已扫描到:["+className+"]");
}
}
/**
* 加载配置文件:application.properties
* @param initParameter
*/
private void doLoadConfig(String initParameter){
//将application.properties加载到输入流
try(InputStream is = this.getClass().getClassLoader().getResourceAsStream(initParameter)) {
properties.load(is);//读取配置文件
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 内部类
* Handler记录Controller中的RequestMapping和Method的对应关系
* 即spring中一个url请求对应一个方法
*/
private class Handler{
protected Object controller; //保存方法对应的实例
protected Method method; //保存映射的方法
protected Pattern pattern; //url对应的正则表达式,用于映射匹配
protected Map<String,Integer> paramIndexMapping; //参数名以及序号
/**
*构造一个Handler基本的参数
* @param controller
* @param method
* @param pattern
*/
public Handler(Object controller, Method method, Pattern pattern) {
this.controller = controller;
this.method = method;
this.pattern = pattern;
paramIndexMapping = new HashMap<String, Integer>();
putParamIndexMapping(method);
}
/**
* 处理方法中的参数
* @param method
*/
private void putParamIndexMapping(Method method) {
//获取方法参数上的注解,注意Annotation是一个二维数组
Annotation[][] p = method.getParameterAnnotations();
for (int i = 0; i < p.length; i++) {
for (Annotation a : p[i]) {
if (a instanceof MyRequestParam){//如果这个注解是MyRequestParam
String param = ((MyRequestParam) a).value();
if (!"".equals(param)){
paramIndexMapping.put(param,i);
}
}
}
}
//提取方法中的request和response参数
Class<?>[] parameterTypes = method.getParameterTypes();//获取所有参数的类型
for (int i = 0; i < parameterTypes.length ; i ++) {
Class<?> type = parameterTypes[i];
if (type == HttpServletRequest.class ||
type == HttpServletResponse.class){
paramIndexMapping.put(type.getName(),i);
}
}
}
@Override
public String toString() {
return "Handler{" +
"controller=" + controller +
", method=" + method +
", pattern=" + pattern +
", paramIndexMapping=" + paramIndexMapping +
'}';
}
}
/**
* HTTP是基于字符串协议,所以url传过来的参数都是String类型的,
* 只需要把String转换为对应方法中的类型就好
* @param type
* @param value
* @return
*/
private Object convert(Class<?> type,String value){
if(Integer.class == type||type == int.class){
return Integer.valueOf(value);
}
//如果还有double或者其他类型,继续加if
//这时候,我们应该想到策略模式了
return value;
}
}
5.测试
启动项目后可以看到,初始化MyDispatcherServlet都做了什么
浏览器地址输入路径:http://localhost:8088/demo/query?name=zhangsan&age=18
访问结果:query success :My name is zhangsan and age is 18