用300行代码手写提炼Spring核心原理(更新中)

1. 自定义配置

1.1 配置application.properties文件

  1. resource文件夹下创建application.properties文件

  2. 配置以下内容

    scanPackage = com.wangmumu.demo
    

1.2 配置web.xml文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dlCExSoZ-1637928812560)(F:\冲冲冲\笔记\pic\1637807011(1)].jpg)

<?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>XL Web Application</display-name>
    <servlet>
        <servlet-name>xlmvc</servlet-name>
        <servlet-class>com.wangmumu.mvcfreamwork.v1.servlet.XLDispatcherServlet</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>xlmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

1.3 自定义注解

@interface与interface的区别以及@interface的用法

java元注解 @Target注解用法

java元注解 @Retention注解使用

java元注解 @Documented注解使用

  • @XLService注解如下
package com.wangmumu.mvcfreamwork.annotation;

import java.lang.annotation.*;

/*
* 用于描述注解的使用范围,
* 用于描述类、接口(包括注解类型) 或enum声明
* Class, interface (including annotation type), or enum declaration
*/
@Target({ElementType.TYPE})
/*
* 定义了该Annotation被保留的时间长短:
* RUNTIME:在运行时有效(即运行时保留)
* */
@Retention(RetentionPolicy.RUNTIME)
/*
* 表明这个注解应该被 javadoc工具记录
* */
@Documented
public @interface XLService {
    String value() default "";
}
  • @XLAutowired注解如下
package com.wangmumu.mvcfreamwork.annotation;

import java.lang.annotation.*;

/*
 * 用于描述注解的使用范围,
 * 用于描述域 Field declaration (includes enum constants)
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XLAutowired {
    String value() default "";
}

  • @XLController注解如下
package com.wangmumu.mvcfreamwork.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XLController {
    String value() default "";
}

  • XLRequestMapping注解如下
package com.wangmumu.mvcfreamwork.annotation;

import java.lang.annotation.*;

/*
 * 用于描述注解的使用范围,
 * 用于描述类、接口(包括注解类型) 或enum声明
 * 用于描述方法 Method declaration
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XLRequestMapping {
    String value() default "";
}

  • XLRequestParam注解如下
package com.wangmumu.mvcfreamwork.annotation;

import java.lang.annotation.*;

/*
 * 用于描述注解的使用范围,
 * 用于描述参数 Formal parameter declaration
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XLRequestParam {
    String value() default "";
}

1.4 配置注解

  • 配置业务实现类DemoService

package com.wangmumu.demo.service;

public interface IDemoService {
    public String get(String name);
}

package com.wangmumu.demo.service.impl;

import com.wangmumu.demo.service.IDemoService;

public class DemoService implements IDemoService {
    @Override
    public String get(String name) {
        return "My name is " + name;
    }
}

  • 配置请求入口类

package com.wangmumu.demo.mvc.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.wangmumu.demo.service.IDemoService;
import com.wangmumu.mvcfreamwork.annotation.XLAutowired;
import com.wangmumu.mvcfreamwork.annotation.XLController;
import com.wangmumu.mvcfreamwork.annotation.XLRequestMapping;
import com.wangmumu.mvcfreamwork.annotation.XLRequestParam;

import java.io.IOException;

@XLController
@XLRequestMapping("/demo")
public class DemoAction {
    @XLAutowired
    private IDemoService demoService;

    @XLRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp, @XLRequestParam("name") String name) {
        String result = demoService.get(name);
        try {
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @XLRequestMapping("/add")
    public void add(HttpServletRequest req, HttpServletResponse resp,
                    @XLRequestParam("a") Integer a, @XLRequestParam("b") Integer b) {
        try {
            resp.getWriter().write(a + "+" + b + "=" + (a+b));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @XLRequestMapping("/remove")
    public void remove(HttpServletRequest req, HttpServletResponse resp, @XLRequestParam("id") Integer id){
    }
}

实现1.0版本

servlet中doGet()和doPost()的用法

Java中Method.invoke方法

HttpServletRequest和HttpServletResponse对象详解

Java Properties 类

Class.forName()用法详解

package com.wangmumu.mvcfreamwork.v1.servlet;

import com.wangmumu.mvcfreamwork.annotation.XLAutowired;
import com.wangmumu.mvcfreamwork.annotation.XLController;
import com.wangmumu.mvcfreamwork.annotation.XLRequestMapping;
import com.wangmumu.mvcfreamwork.annotation.XLService;

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.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class XLDispatcherServlet extends HttpServlet {
    //key:请求URL,value:控制器、方法
    //key:类名class,value:类实例对象
    private Map<String, Object> mapping = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{
        this.doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{
        try{
            doDispatch(req, resp);
        }catch (Exception e){
            resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace()));
        }
    }

    //把请求分发到控制器
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        //该方法用于获取请求行中的资源名称部分即位于 URL 的主机和端门之后、参数部分之前的部分
        String url = req.getRequestURI();
        //该方法用于获取请求 URL 中属于 Web 应用程序的路径,这个路径以 / 开头,表示相对于整个 Web 站点的根目录,路径结尾不含 /。
        // 如果请求 URL 属于 Web 站点的根目录,那么返回结果为空字符串("")
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        if (!this.mapping.containsKey(url)){
            resp.getWriter().write("404 Not Found!!");
            return;
        }
        Method method = (Method) this.mapping.get(url);
        Map<String, String[]> params = req.getParameterMap();
        //method.getDeclaringClass()方法返回表示声明由此Method对象表示的方法的类的Class对象。
        //method.getDeclaringClass().getName()获取类名
        method.invoke(this.mapping.get(method.getDeclaringClass().getName()), new Object[]{req, resp, params.get("name")[0]});
    }

    //mvc框架初始化
    public void init(ServletConfig config) throws ServletException{
        InputStream is = null;
        try{
            //Properties 继承于 Hashtable。表示一个持久的属性集.属性列表中每个键及其对应值都是一个字符串。
            Properties configContext = new Properties();
            //Class.getClassLoader.getResourceAsStream(String path)
            //默认是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
            //根据相对地址找到文件并转换为输入流
            is = this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation"));
            //Properties.load从输入流中读取属性列表(键和元素对)。
            configContext.load(is);
            String scanPackage  = configContext.getProperty("scanPackage");
            //扫描包,获取所有类名,并存入mapping
            doScanner(scanPackage);
            //根据类名填充mapping的类的value值
            for (String className : mapping.keySet()){
                //如果不包含子集目录
                if (!className.contains("."))
                    continue;
                //Class.forName返回一个给定类或者接口的一个 Class 对象
                Class<?> clazz = Class.forName(className);
                //clazz.isAnnotationPresent判断指定类型的注释存在于此元素上
                //判断是否存在@Controller注释
                if (clazz.isAnnotationPresent(XLController.class)){
                    //根据类名填充mapping的类的value值
                    mapping.put(className, clazz.newInstance());
                    String baseUrl = "";
                    //判断类是否存在@RequestMapping注释,如果存在则获取baseurl
                    if (clazz.isAnnotationPresent(XLRequestMapping.class)){
                        XLRequestMapping requestMapping = clazz.getAnnotation(XLRequestMapping.class);
                        baseUrl = requestMapping.value();
                    }
                    //获取类中所有方法
                    Method[] methods = clazz.getMethods();
                    for (Method method : methods){
                        //判断方法是否存在RequestMapping注解,如果存在则拼接url,并将<url,method>存入mapping
                        if (!method.isAnnotationPresent(XLRequestMapping.class)){
                            continue;
                        }
                        XLRequestMapping requestMapping = method.getAnnotation(XLRequestMapping.class);
                        String url = (baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                        mapping.put(url, method);
                        System.out.println("Mapped" + url + "," + method);
                    }
                }
                //判断是否存在@Service注释
                else if (clazz.isAnnotationPresent(XLService.class)){
                    XLService service = clazz.getAnnotation(XLService.class);
                    //如果beanName不为空则为自定义beanName
                    String beanName = service.value();
                    //如果beanName为空则为className
                    if ("".equals(beanName)){
                        beanName = clazz.getName();
                    }
                    //将<beanName,instance>存入mapping
                    Object instance = clazz.newInstance();
                    mapping.put(beanName, instance);
                    //如果该类实现接口,同样将将<beanName,instance>存入mapping
                    for (Class<?> i : clazz.getInterfaces()){
                        mapping.put(i.getName(), instance);
                    }
                }else {
                    continue;
                }
            }
            //遍历键值对中的值,以保证class内中所有的字段也被存储到spring中
            for (Object object : mapping.values()){
                if (object == null){
                    continue;
                }
                Class clazz = object.getClass();
                if (clazz.isAnnotationPresent(XLController.class)){
                    //得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
                    Field[] fields = clazz.getDeclaredFields();
                    for (Field field : fields){
                        if (!field.isAnnotationPresent(XLAutowired.class)){
                            continue;
                        }
                        XLAutowired autowired = field.getAnnotation(XLAutowired.class);
                        String beanName = autowired.value();
                        if ("".equals(beanName)){
                            beanName = field.getType().getName();
                        }
                        //setAccessible功能是启用或禁用安全检查
                        field.setAccessible(true);
                        try{
                            //将指定对象变量上此 Field 对象表示的字段设置为指定的新值.
                            field.set(mapping.get(clazz.getName()), mapping.get(beanName));
                        }catch (IllegalAccessException e){
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (is != null){
                try{is.close();} catch (IOException e){ e.printStackTrace();}
            }
        }
    }

    //扫描properties文件中记录的需要扫描的package,获取所有类名
    private void doScanner(String scanPackage) {
        //扫描包地址
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        //获取该包下所有文件
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()){
            if (file.isDirectory()){//如果是文件夹就进行递归扫描
                doScanner(scanPackage + "." + file.getName());
            }else {
                //如果不是class文件则跳过
                if (!file.getName().endsWith(".class")){
                    continue;
                }
                //获取类名
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
                //将<类名,null>存入mapping
                mapping.put(clazzName, null);
            }
        }
    }
}

实现2.0版本

Method.getParameterAnnotations()研究

在这里插入图片描述

package com.wangmumu.mvcfreamwork.v2.servlet;

import com.wangmumu.mvcfreamwork.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.Member;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class XLDispatcherServlet2 extends HttpServlet {
    //声明全局成员变量
    //保存application.properties配置文件中的内容
    private Properties contextConfig = new Properties();
    //保存扫描的所有的类名
    private List<String> classNames = new ArrayList<>();
    //IoC容器
    private Map<String, Object> ioc = new HashMap<>();
    //保存url和Method的对应关系
    private Map<String, Method> handlerMapping = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    //doPost使用了委派模式,委派模式的具体逻辑在doDispatch()方法中实现
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try{
            doDispatch(req, resp);
        }catch (Exception e){
            e.printStackTrace();
            resp.getWriter().write("500 Exection, Detail: "+ Arrays.toString(e.getStackTrace()));
        }
    }

    //以下代码中,doDispatch()虽然完成了动态委派并进行了反射调用,但对url参数的处理还是静态的。
//    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
//        String url = req.getRequestURI();
//        String contextPath = req.getContextPath();
//        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
//        if (!this.handlerMapping.containsKey(url)){
//            resp.getWriter().write("404 Not Found !!");
//            return;
//        }
//        Method method = this.handlerMapping.get(url);
//        Map<String, String[]> params = req.getParameterMap();
//        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//        //第一个参数:方法所在的实例
//        //第二个参数:调用时所需的实参
//        method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]});
//        System.out.println(method);
//    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
        if (!this.handlerMapping.containsKey(url)){
            resp.getWriter().write("404 Not Found !!");
            return;
        }
        Method method = this.handlerMapping.get(url);
        Map<String, String[]> params = req.getParameterMap();
        //获取方法的形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        //保存请求的url参数列表
        Map<String, String[]> parameterMap = req.getParameterMap();
        //保存赋值参数的位置
        Object[] parameterValues = new Object[parameterTypes.length];
        //根据参数位置动态赋值
        for (int i=0; i< parameterTypes.length; i++){
            Class parameterType = parameterTypes[i];
            if (parameterType == HttpServletRequest.class){
                parameterValues[i] = req;
                continue;
            }
            else if( parameterType == HttpServletResponse.class){
                parameterValues[i] = resp;
                continue;
            }
            else if (parameterType == String.class){
                //提取方法中加了注解的参数
                Annotation[][] pa = method.getParameterAnnotations();
                for (int j=0; j< pa.length; j++){
                    for (Annotation a : pa[i]){
                        if (a instanceof XLRequestParam){
                            String paramName = ((XLRequestParam)a).value();
                            if (!"".equals(paramName.trim())){
                                String value = Arrays.toString(parameterMap.get(paramName))
                                        .replaceAll("\\[|\\]", "")
                                        .replaceAll("\\s", ",");
                                parameterValues[i] = value;
                            }
                        }
                    }
                }
            }
        }
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        //第一个参数:方法所在的实例
        //第二个参数:调用时所需的实参
        method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]});
        System.out.println(method);
    }


    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2.扫描相关类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3.初始化扫描到的类,并将它们放到IoC容器中
        doInstance();
        //4.完成依赖注入
        doAutowired();
        //5.初始化HandlerMapping
        initHandlerMapping();

    }


    //1.加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        //直接通过类路径找到Spring主配置文件所在的路径
        //并且将其读取出来放到Properties对象中
        //相当于将scanPackage=com.wangmumu.demo保存到了内存中
        //Class.getClassLoader.getResourceAsStream(String path)
        //默认是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
        //根据相对地址找到文件并转换为输入流
        InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //2.扫描相关类
    private void doScanner(String scanPackage) {
        //scanPackage 存储的是需要扫描的包路径
        //将包路径转换为文件路径,实际上就是把.替换成/
        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());
            }else {
                if (!file.getName().endsWith(".class")){
                    continue;
                }
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(className);
            }
        }
    }

    //3.初始化扫描到的类,并且将它们放入IoC容器中
    private void doInstance() {
        if (classNames.isEmpty()){
            return;
        }
        try {
            for (String className : classNames){
                Class<?> clazz = Class.forName(className);
                //加了注解的类才需要初始化
                if (clazz.isAnnotationPresent(XLController.class)){
                    Object instance = clazz.newInstance();
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                }
                else if (clazz.isAnnotationPresent(XLService.class)){
                    //1. 自定义的beanName
                    XLService service = clazz.getAnnotation(XLService.class);
                    String beanName = service.value();
                    //2. 默认类名首字母小写
                    //String.trim去除字符串的头尾空格
                    if ("".equals(beanName.trim())){
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);
                    //3. 根据类型自动赋值,这是投机取巧的方法
                    for (Class<?> i : clazz.getInterfaces()){
                        if (ioc.containsKey(i.getName())){
                            throw new Exception("The “" + i.getName() +"“ is exist!!");
                        }
                        //把接口的类型直接当成key
                        ioc.put(i.getName(), instance);
                    }
                }
                else {
                    continue;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //4.完成依赖注入
    private void doAutowired() {
        if (ioc.isEmpty()){
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()){
            //获取所有的字段,包括private、protected、default类型的
            //正常来说,普通的OOP编程只能获得public类型的字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields){
                if (!field.isAnnotationPresent(XLAutowired.class)){
                    continue;
                }
                XLAutowired autowired = field.getAnnotation(XLAutowired.class);

                //如果用户没有自定义beanName,默认就根据类型注入
                //这个地方省去了对类名首字母小写的情况的判断
                String beanName = autowired.value().trim();
                if ("".equals(beanName)){
                    beanName = field.getType().getName();
                }
                //如果是public以外的类型,只要加了@Autowired注解都要强制赋值
                //反射中叫做暴力访问
                field.setAccessible(true);
                try {
                    //使用反射机制动态给字段赋值
                    //给entry.getValue()的field字段赋值ioc.get(beanName)
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //5.初始化HandlerMapping
    //初始化HandlerMapping就是策略模式的应用案例
    //初始化url和Method的一对一关系
    private void initHandlerMapping() {
        if (ioc.isEmpty()){
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()){
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(XLController.class)){
                continue;
            }
            //保存写在类上面的requestmapping
            String baseUrl = "";
            if (clazz.isAnnotationPresent(XLRequestMapping.class)){
                XLRequestMapping requestMapping = clazz.getAnnotation(XLRequestMapping.class);
                baseUrl = requestMapping.value();
            }
            //默认获取所有的public类型的方法
            for (Method method : clazz.getMethods()){
                if (!method.isAnnotationPresent(XLRequestMapping.class)){
                    continue;
                }
                XLRequestMapping requestMapping = method.getAnnotation(XLRequestMapping.class);
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.println("Mapped :" + url + "," + method);
            }
        }
    }



    //3.1 类名首字母小写
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        //之所以要做加法,是因为大、小写字母的ASCII码相差32
        //而且大写字母的ASCII码要小于小写字母的ASCII码
        //在java中,对char做算术运算实际上就是对ASCII码做算术运算
        chars[0] += 32;
        return String.valueOf(chars);
    }


}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值