手写spring框架,帮你彻底了解spring的工作原理机制

在我们的日常工作中,经常会用到Spring、Spring Boot、Spring Cloud、Struts、Mybatis、Hibernate等开源框架,有了这些框架的诞生,平时的开发工作量也是变得越来越轻松,我们用 Spring Boot 分分钟可以新建一个Web项目。今天通过手写Spring框架,帮大家深入了解一下Spring的工作机制,文中涉及的代码只用来帮助大家理解Spring,不会在线上使用,有不严谨的地方还请大家掠过。

项目结构
在这里插入图片描述

框架部分实现

为了区分框架部分代码和业务部分代码,我们将这两部分分别划分在不同的包内 com.mars.demo 和 com.mars.framework,以便随后只扫描业务代码。
2. 这里是自己手写Spring框架,所以不会引入任何Spring项目相关的包。

3.由于是一个Web项目,所有我们需要引入 servlet-api 包,仅供编译器使用,所有配置 scope 为 provided。

新建一个Servlet

首先新建一个 HttpServlet 的实现类 MarsDispatcherServlet,用来接收请求。

public class MarsDispatcherServlet extends HttpServlet {

@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 {
    //6. 处理请求
}

@Override
public void init(ServletConfig config) throws ServletException {

}

配置web.xml

Spring Mvc Education
<servlet>
    <servlet-name>marsmvc</servlet-name>
    <servlet-class>com.mars.framework.servlet.MarsDispatcherServlet</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>marsmvc</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

首先配置了一个 servlet, 名字是 marsmvc, 类全路径是 com.mars.framework.servlet.MarsDispatcherServlet。
设置了初始化参数名和值(这里的值是整个项目的配置文件)。
配置 load-on-startup, 标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。
配置 servlet-mapping, 将所有请求转发到这个servlet处理。
配置application.properties

scanPackage=com.mars.demo

这个比较好理解,仅配置了一项内容,意思是要扫描的包,随后我们会获取这个值去加载容器。

定义我们常用的注解

MarsAutowired
MarsController
MarsRequestMapping
MarsRequestParam
MarsService
这里仅列举两个,其他都大同小异,需要源码的可以去我的代码仓库fork。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsController {
String value() default “”;
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsRequestMapping {
String value() default “”;
}

充实Servlet功能

先列出框架在初始化的时候都要做那些事情

加载配置文件
扫描所有相关联的类
初始化所有相关联的类,并且将其保存在IOC容器里面
执行依赖注入(把加了@Autowired注解的字段赋值)
构造HandlerMapping,将URL和Method进行关联

接下来我们一步步完成上面的操作

@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("===================");
//1.加载配置文件
doLoadConfig(config.getInitParameter(“contextConfigLocation”));

    //2.扫描所有相关联的类
    doScanner(contextConfig.getProperty("scanPackage"));
    
    //3.初始化所有相关联的类,并且将其保存在IOC容器里面
    doInstance();
    
    //4.执行依赖注入(把加了@Autowired注解的字段赋值)
    doAutowired();

    //Spring 和核心功能已经完成 IOC、DI
    
    //5.构造HandlerMapping,将URL和Method进行关联
    initHandlerMapping();

    System.out.println("Mars MVC framework initialized");

}

加载配置文件

private Properties contextConfig = new Properties();

private void doLoadConfig(String location) {
    InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);

    try {
        contextConfig.load(inputStream);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

扫描所有相关联的类

private void doScanner(String basePackage) {
//获取要扫描包的url
URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\.", “/”));

    File dir = new File(url.getFile());
    //遍历包下面所有文件
    for(File file: dir.listFiles()) {
        if(file.isDirectory()){
            //递归扫描
            doScanner(basePackage + "." + file.getName());
        } else {
            String className = basePackage + "." + file.getName().replace(".class", "");

            classNames.add(className);

            System.out.println(className);
        }
    }

}

初始化所有相关联的类,并且将其保存在IOC容器里面
private void doInstance() {

    if(classNames.isEmpty()) return;

    for(String className: classNames) {

        try {
            Class<?> clazz = Class.forName(className);


            if(clazz.isAnnotationPresent(MarsController.class)) {

                Object instance = clazz.newInstance();
                String beanName = lowerFirstCase(clazz.getSimpleName());
                ioc.put(beanName, instance);

            } else if (clazz.isAnnotationPresent(MarsService.class)) {

                MarsService service = clazz.getAnnotation(MarsService.class);

                //2.优先使用自定义命名
                String beanName = service.value();

                if("".equals(beanName.trim())) {
                    //1.默认使用类名首字母小写
                    beanName = lowerFirstCase(clazz.getSimpleName());
                }

                Object instance = clazz.newInstance();

                ioc.put(beanName, instance);

                //3.自动类型匹配(例如:将实现类赋值给接口)

                Class<?> [] interfaces = clazz.getInterfaces();

                for(Class<?> inter: interfaces) {
                    ioc.put(inter.getName(), instance);
                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

//利用ASCII码的差值
private String lowerFirstCase(String str) {
    char[] chars = str.toCharArray();
    chars[0] += 32;
    return String.valueOf(chars);
}

执行依赖注入(把加了@Autowired注解的字段赋值)

private void doAutowired() {

    if(ioc.isEmpty()) return;

    for(Map.Entry<String, Object> entry: ioc.entrySet()) {
        //注入的意思就是把所有的IOC容器中加了@Autowired注解的字段赋值
        //包含私有字段
        Field[] fields = entry.getValue().getClass().getDeclaredFields();

        for(Field field : fields) {

            //判断是否加了@Autowired注解
            if(!field.isAnnotationPresent(MarsAutowired.class)) continue;

            MarsAutowired autowired = field.getAnnotation(MarsAutowired.class);

            String beanName = autowired.value();

            if("".equals(beanName)) {
                beanName = field.getType().getName();
            }

            //如果这个字段是私有字段的话,那么要强制访问
            field.setAccessible(true);
            try {
                field.set(entry.getValue(), ioc.get(beanName));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

构造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(MarsController.class)) continue;

        String baseUrl = "";

        if(clazz.isAnnotationPresent(MarsRequestMapping.class)) {
            MarsRequestMapping requestMapping = clazz.getAnnotation(MarsRequestMapping.class);
            baseUrl = requestMapping.value();
        }

        Method[] methods = clazz.getMethods();

        for(Method method : methods) {

            if(!method.isAnnotationPresent(MarsRequestMapping.class)) continue;

            MarsRequestMapping requestMapping = method.getAnnotation(MarsRequestMapping.class);

            String regex = requestMapping.value();

            regex = (baseUrl + regex).replaceAll("/+", "/");

            Pattern pattern = Pattern.compile(regex);
            handlerMapping.add(new Handler(entry.getValue(), method, pattern));

            System.out.println("Mapping: " + regex + "," + method.getName());
        }
    }

}

编写业务代码

新建一个Controller

@MarsController
@MarsRequestMapping("/demo")
public class DemoApi {

@MarsAutowired
private DemoService demoService;

@MarsRequestMapping("/query")
public void query(HttpServletRequest req,
                  HttpServletResponse resp,
                  @MarsRequestParam("name") String name) {
    System.out.println("name: " + name);
    String result = demoService.get(name);

    try{
        resp.getWriter().write(result);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

@MarsRequestMapping("/add")
public void add(HttpServletRequest req,
                HttpServletResponse resp,
                @MarsRequestParam("a") Integer a,
                @MarsRequestParam("b") Integer b) {
    try {
        resp.getWriter().write(String.format("%d+%d=%d", a, b, (a+b)));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}

提供两个接口,一个通过请求名称返回响应的介绍内容,另一个将请求的两个Integer相加并返回。

创建一个Service

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

@MarsService
public class DemoServiceImpl implements DemoService {
public String get(String name) {
return String.format(“My name is %s.”, name);
}
}

添加Jetty插件

我们的项目运行在Jetty中,所以添加相关插件以及配置:

org.mortbay.jetty jetty-maven-plugin 7.1.6.v20100715 9988 foo 5 8080 60000 /

运行
在这里插入图片描述

点击 jetty:run 运行项目
在这里插入图片描述

浏览器访问: http://localhost:8080/demo/query?name=Mars

在这里插入图片描述

浏览器访问:http://localhost:8080/demo/add?a=10&b=20

这样一个完整的spring框架就已经手写出来了,大家也可以关注下我的宫众浩【java架构君】为大家准备好了2019最新的面试教程和架构师资料,感谢大家的支持与关注!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值