300行代码实现mini版Spring

目标实现的功能

  • 常用的注解
  • 根据web.xml的配置,扫描配置文件和相关的包的类
  • IoC容器
  • DI注入
  • HandlerMapping

实现思路

在这里插入图片描述

1、配置阶段

配置web.xml

设定init-param

需要配置好servlet-class也就是DispatcherServlet类的全地址,init-param中配置好配置文件

<servlet>
    <servlet-name>jjmvc</servlet-name>
    <servlet-class>com.jixiaotian.spring.framework.v2.JJDispatcherServlet</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>

设定url-pattern

<servlet-mapping>
    <servlet-name>jjmvc</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

一个完整的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>JJJJJJ Web Application</display-name>

    <servlet>
        <servlet-name>jjmvc</servlet-name>
        <servlet-class>com.jixiaotian.spring.framework.v2.JJDispatcherServlet</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>jjmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

一些常用的注解

@JJAutowired

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

@JJController

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

@JJRequestMapping

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

@JJRequestParam

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

@JJService

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

2、初始化阶段

此版本先考虑提取核心功能,因此先简单实现了加载配置、IoC、DI、MVC功能

继承HttpServlet类,并重写init()方法

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

    //1、加载配置文件
    doLoadConfig(config.getInitParameter("contextConfigLocation"));

    //2、扫描相关的类
    doScanner(contextConfig.getProperty("scanPackage"));

    //================Ioc部分====================
    //3、初始化IoC容器,将扫描到的相关的类实例化,保存到IoC容器中
    System.out.println("初始化IoC容器:");
    doInstance();

    // 若要加入AOP,在DI之前  加入AOP,新生成的代理对象
    //================DI部分====================
    //4、完成依赖注入
    doAutowired();

    //================MVC部分====================
    //5、初始化HandlerMapping
    doInitHandlerMapping();

    System.out.println("JJ Spring framework is init.");

}

加载配置文件

private void doLoadConfig(String contextConfigLocation) {
    System.out.println("加载配置文件:" + contextConfigLocation);
    InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
    try {
        contextConfig.load(is);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(null != is) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

根据配置文件,递归扫描所有相关的类(只扫描配置目录下所有.class文件),将类名保存下来以供后面IoC容器初始化使用

private void doScanner(String scanPackage) {
    URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
    File classPath = new File(url.getFile());
    //当成是一个ClassPath文件夹
    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", "");
            System.out.println(className);
            //使用Class.forName(className)反射
            classNames.add(className);
        }
    }
}

初始化IoC(Inversion of Control)容器

根据上一步扫描到的所有类名进行反射,将标注有@Controller或@Service注解的类加入IoC容器。在实例化时,优先选用自定义名,若无自定义名,则使用类名首字母小写;如果类是接口,则实例化它的实现类,但必须实现类只能有一个,否则将会报错抛出异常

private void doInstance() {
    if(classNames.isEmpty()) {return;}

    try {
        for (String className : classNames) {
            Class<?> clazz = Class.forName(className);

            //没加注解,控制权不翻转,自己管自己
            if(clazz.isAnnotationPresent(JJController.class)) {
                //把需要存入IoC容器中的key提取出来了,把value也搞出来了(实例化对象)
                String beanName = toLowerFirstCase(clazz.getSimpleName());
                Object instance = clazz.newInstance();
                System.out.println("beanName: " + beanName);
                ioc.put(beanName,instance);
            } else if(clazz.isAnnotationPresent(JJService.class)) {
                //1、在多个包下出现相同的类名,只能自己起名字(JJService(name))
                //自定义命名
                String beanName = clazz.getAnnotation(JJService.class).value();

                //2、默认的类名首字母小写
                if("".equals(beanName.trim())) {
                    beanName = toLowerFirstCase(clazz.getSimpleName());
                }
                Object instance = clazz.newInstance();
                System.out.println("beanName: " + beanName);
                ioc.put(beanName,instance);

                //3、如果是接口
                //判断有多少个实现类,如果只有一个,默认选择这个实现类
                //如果有多个,只能抛异常
                //实现思路:以当前扫描到的类的接口类名为key值,以当前类的实例为value,加入到IoC容器中
                //        若再扫描到此接口的实现类,那么抛出异常
                for (Class<?> i : clazz.getInterfaces()) {
                    if(ioc.containsKey(i.getName())){
                        throw new Exception("The " + i.getName() + " is exists!");
                    }
                    System.out.println("beanName: " + beanName);
                    ioc.put(i.getName(),instance);
                }
            } else {
                continue;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

DI(Dependency Injection)依赖注入

遍历IoC容器中的实例对象,对于标注@Autowired的字段,打开字段的访问权限,若注解中有自定义beanName则使用自定义,若没有自定义beanName则默认根据字段类型(即需要进行DI的对象名,这里的自定义beanName需要与IoC容器中的beanName保持一致),再将对应类的需要DI的字段的值设置为IoC容器中的对象实例

private void doAutowired() {
    if (ioc.isEmpty()) {return;}

    for (Map.Entry<String, Object> entry : ioc.entrySet()) {

        //把所有的包括private/protected/default/public 修饰的字段都取出来
        for (Field field : entry.getValue().getClass().getDeclaredFields()) {
            if(!field.isAnnotationPresent(JJAutowired.class)) { continue; }

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

            //如果用户没有自定义的beanName,就默认根据类型注入
            String beanName = autowired.value().trim();

            if("".equals(beanName)) {
                //获取字段的类型
                beanName = field.getType().getName();
            }

            //暴力访问
            field.setAccessible(true);

            try {
                //ioc.get(beanName) 通过接口的全名拿到接口的实现类的实例
                field.set(entry.getValue(),ioc.get(beanName));
                /*
                field.set(Object obj, Object value);
                将obj对象上的field字段设置为value
                例如AController中有一个加了@Autowired注解的aService字段
                那么将aController对象的aService字段的值设置为value
                 */
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

MVC部分,初始化HandlerMapping,存储url和method的映射关系。获取标注有@Controller注解的类,方法的url使用类的@RequestMapping的值+方法的@RequestMapping的值。

private void doInitHandlerMapping() {
    if (ioc.isEmpty()) {return;}

    for (Map.Entry<String, Object> entry : ioc.entrySet()) {
        Class<?> clazz = entry.getValue().getClass();

        if(!clazz.isAnnotationPresent(JJController.class)) {continue;}
        //提取class上配置的url
        String baseUrl = "";
        if(clazz.isAnnotationPresent(JJRequestMapping.class)) {
            JJRequestMapping requestMapping = clazz.getAnnotation(JJRequestMapping.class);
            baseUrl = requestMapping.value();
        }

        //只获取public的方法
        for (Method method : clazz.getMethods()) {
            if(!method.isAnnotationPresent(JJRequestMapping.class)) {continue;}
            //提取每个方法上面的url
            JJRequestMapping requestMapping = method.getAnnotation(JJRequestMapping.class);
            //正则  解释器模式
            String url =  ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+","/");
            handlerMapping.put(url,method);
            System.out.println("Mapped : " + url + ", " + method);
        }
    }
}

3、运行阶段

调用doPost()/doGet()方法,获取到HttpServletRequest和HttpServletResponse

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //6、委派,根据URL去找到一个对应的Method并通过response返回
    try {
        doDispath(req,resp);
    } catch (Exception e) {
        e.printStackTrace();
        resp.getWriter().write("500 Exception, Detail : " + Arrays.toString(e.getStackTrace()));
    }
}

调用doDispath()方法,通过HttpServletRequest中的请求地址,获取HandlerMapping中的方法。

若HandlerMapping中没有存储这个url,则404。可以根据url获取到方法的话,则调用相关的方法

private void doDispath(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    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;
    }

    Map<String, String[]> params = req.getParameterMap();

    Method method = this.handlerMapping.get(url);

    //获取形参列表
    Class<?>[] parameterTypes = method.getParameterTypes();
    Object [] paramValues = new Object[parameterTypes.length];

    for (int i = 0; i < parameterTypes.length; i++) {
        Class<?> parameterType = parameterTypes[i];
        if(parameterType == HttpServletRequest.class){
            paramValues[i] = req;
        } else if (parameterType == HttpServletResponse.class) {
            paramValues[i] = resp;
        } else if (parameterType == String.class) {
            //通过运行时的状态去拿到注解的值
            Annotation[][] pa = method.getParameterAnnotations();
            for (int j = 0; j < pa.length; j++) {
                for (Annotation a : pa[j]) {
                    if(a instanceof  JJRequestParam) {
                        String paramName = ((JJRequestParam) a).value();
                        if(!"".equals(paramName.trim())){
                            String value = Arrays.toString(params.get(paramName))
                                .replaceAll("\\[\\]", "")
                                .replaceAll("\\s+","");
                            paramValues[i] = value;

                        }
                    }
                }
            }
        }
    }

    //两个参数,方法所在的对象,方法的参数
    String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
    //暂时硬编码
    //赋值实参列表
    method.invoke(ioc.get(beanName), new Object[]{req,resp,params.get("name")[0]});
}

测试时用到的其他相关的类

接口IDemoService

package com.jixiaotian.spring.demo.service;

/**
 * @Author: jixiaotian.ch@qq.com
 * @Date : 2020/9/8
 */
public interface IDemoService {

    String get(String name);
}

实现类DemoService

package com.jixiaotian.spring.demo.service.impl;

import com.jixiaotian.spring.framework.annotation.JJService;
import com.jixiaotian.spring.demo.service.IDemoService;

/**
 * @Author: jixiaotian.ch@qq.com
 * @Date : 2020/9/8
 */
@JJService
public class DemoService implements IDemoService {
    @Override
    public String get(String name) {
        return "My name is " + name + ",from service.";
    }
}

Controller(仅测试功能)

package com.jixiaotian.spring.demo.mvc.action;

import com.jixiaotian.spring.demo.service.IDemoService;
import com.jixiaotian.spring.framework.annotation.JJAutowired;
import com.jixiaotian.spring.framework.annotation.JJController;
import com.jixiaotian.spring.framework.annotation.JJRequestMapping;
import com.jixiaotian.spring.framework.annotation.JJRequestParam;
import com.jixiaotian.spring.demo.service.impl.DemoService;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@JJController
@JJRequestMapping("/demo")
public class DemoAction {

  	@JJAutowired private IDemoService demoService;

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

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

	@JJRequestMapping("/sub")
	public void add(HttpServletRequest req, HttpServletResponse resp,
					@JJRequestParam("a") Double a, @JJRequestParam("b") Double b){
		try {
			resp.getWriter().write(a + "-" + b + "=" + (a - b));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@JJRequestMapping("/remove")
	public String  remove(@JJRequestParam("id") Integer id){
		return "" + id;
	}

}

两个高频面试题

SpringIoC、DI、MVC的基本执行原理

  • IoC:Inversion of Control(控制反转),使用IoC容器,保存Spring管理的所有对象

    实现步骤:

    • 1) doScanner() 加载web.xml、application.properties配置文件,获取到需要扫描的包路径,扫描路径下所有.class文件,将需要IoC容器管理的相关类进行记录(享元模式,缓存),以便于初始化IoC容器时使用类名进行反射获取实例。
      1. doInstance() 根据扫描结果将需要IoC管理的类进行初始化获得实例,将使用了@Controller或@Service注解的类进行反射,若注解中定义了beanName则优先使用自定义beanName,否则使用小驼峰格式的类名作为beanName。初始化好实例后存入IoC容器中。
  • DI:Dependency Injection(依赖注入) 需要使用IoC容器中的对象时,需要使用@Autowired注解进行声明,当对象之间有依赖关系时,可以利用Spring保存的对象关系。

    目标:spring保存的对象依赖关系,将对象从IoC容器中取出,进行注入。

    实现步骤:

    • 1)判断哪些对象需要注入。

      方法:读取配置文件或注解(Autowired),判断出需要哪些对象时需要注入的。

    • 2)怎么注入。

      方法:如果用户有声明beanName,直接从IoC容器中取出相应的对象进行注入,若没有,根据类型进行注入。(此处一个接口只能又一个实现类,若有多个,且都交由Spring管理,且未自定义唯一的beanName,则在IoC步骤会报错抛出异常)

  • MVC:Model-View-Controller

    model 指模型表示业务规则

    View 指用户交互的页面

    controller 指控制器接受用户的输入并调用模型和视图去完成用户的需求

    MVC的目的 将M和V的代码进行分离,而使用SpringMVC的根本就是存储url和Method的对应关系,实现传入url调用指定控制器,调用对应的方法,返回相应的view

    实现步骤:

    • 1)找到需要在handlerMapping中记录的类和方法:扫描注解和配置文件,扫描@Controller类,读取类以及方法的RequestMapping中的值

      1. 将url和Method的对应关系存储到handlerMapping中:将类的RequestMapping的值+方法的RequestMapping中的值拼接成url,以url作为Key,method作为Value,存储到handlerMapping中
    • 3)用户调用11111111

      基于Servlet实现,在doPost()方法中执行doDispatch()方法通过url从handlerMapping中找到指定的方法,使用反射机制进行调用方法

Spring中的Bean是线程安全的吗?为什么?

先来仔细看这个题目,问Spring中的Bean是线程安全的吗?

首先要思考一下另一个问题,Spring中的Bean是从哪里来的?是从IoC容器中来的

那么,IoC容器中的Bean是从哪里来的? 是通过配置得来的

那么配置的Bean是哪里来的? 是自己写的

那么原问题Spring中的Bean是线程安全的吗?其实就是在问,你自己写的Bean是线程安全的吗?

那么这个Bean是否是线程安全的,当然是我们自己决定的,这和Spring其实并没有什么关系。

也就是说,Spring中的Bean是否线程安全和Spring无关,因为Spring只承担了创建和管理Bean的职责,而并没有对Bean进行任何修改

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值