java300行程序代码_MINI版Sping-300行代码手写Sping

用不到 300 行代码来描述 Spring IOC、DI、MVC 的精华设计思想,并保证基本功能完整。

Spring 的三个阶段,配置阶段、初始化阶段和运行阶段

3eb9a656c86b

image.png

配置阶段:主要是完成 application.xml 配置和 Annotation 配置。

初始化阶段:主要是加载并解析配置信息,然后,初始化 IOC 容器,完成容器的 DI 操作,已经完成 HandlerMapping 的初始化。

运行阶段:主要是完成 Spring 容器启动以后,完成用户请求的内部调度,并返回响应结果。

项目结构

3eb9a656c86b

image.png

一、配置阶段

1、pom.xml配置

我采用的是 maven 管理项目。先来看 pom.xml 文件中的配置,我只引用了 servlet-api 的依赖。

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

sz-spring

com.suzao.spring

1.0-SNAPSHOT

4.0.0

spring-demo-01

javax.servlet

servlet-api

2.5

war

webapp

org.apache.maven.plugins

maven-war-plugin

3.2.2

src/main/webapp/WEB-INF

WEB-INF

2、SZDispatcherServlet 类

然后,创建 SZDispatcherServlet 类并继承 HttpServlet,重写 init()、doGet() 和 doPost() 方法。

package com.suzao.mvcframework.servlet.v2;

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.IOException;

/**

* @ClassName SZDispatchServlet

* @Description: TODO

* @Author mc

* @Date 2020

* @Version V1.0

**/

public class SZDispatchServlet extends HttpServlet {

@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

super.doGet(req, resp);

}

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

super.doPost(req, resp);

}

@Override

public void init(ServletConfig config) throws ServletException {

super.init(config);

}

}

3、web.xml配置

在 web.xml 文件中配置以下信息:

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">

SuZao Web Application

szmvc

com.suzao.mvcframework.servlet.v2.SZDispatchServlet

contextConfigLocation

application.properties

1

szmvc

/*

4、application.properties配置

我们配置了一个初始化加载的 Spring 主配置文件路径,在原生框架中,我们应该配置的是 classpath:application.xml。在这里,我们为了简化操作,用 properties 文件代替 xml 文件。以下是 properties 文件中的内容:

scanPackage=com.suzao.demo

5、创建SZController 注解:

package com.suzao.mvcframework.annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SZController {

String value() default "";

}

6、创建SZService 注解

package com.suzao.mvcframework.annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SZService {

String value() default "";

}

7、创建SZRequestMapping注解

package com.suzao.mvcframework.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SZRequestMapping {

String value() default "";

}

8、创建SZRequestParam注解

package com.suzao.mvcframework.annotation;

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SZRequestParam {

String value() default "";

}

9、创建SZAutowired 注解

package com.suzao.mvcframework.annotation;

import java.lang.annotation.*;

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SZAutowired {

String value() default "";

}

10、使用自定义注解进行配置DemoAction

package com.suzao.demo.action;

import com.suzao.demo.service.IDemoService;

import com.suzao.mvcframework.annotation.SZAutowired;

import com.suzao.mvcframework.annotation.SZController;

import com.suzao.mvcframework.annotation.SZRequestMapping;

import com.suzao.mvcframework.annotation.SZRequestParam;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

* @ClassName DemoAction

* @Description: TODO

* @Author mc

* @Date 2020

* @Version V1.0

**/

@SZController

@SZRequestMapping("/demo")

public class DemoAction {

@SZAutowired

private IDemoService demoService;

@SZRequestMapping("query")

public void query(HttpServletRequest req, HttpServletResponse resp,

@SZRequestParam("name") String name){

String result = demoService.get(name);

try {

resp.getWriter().write(result);

}catch (Exception e){

e.printStackTrace();

}

}

@SZRequestMapping("add")

public void add(HttpServletRequest request , HttpServletResponse resp,

@SZRequestParam("a") Integer a, @SZRequestParam("b") Integer b){

try {

resp.getWriter().write(a+"+" +b +"=" +(a+b));

}catch (Exception e){

e.printStackTrace();

}

}

@SZRequestMapping("remove")

public void remove(HttpServletRequest req , HttpServletResponse resp,

@SZRequestParam("id") Integer id){

}

}

11、使用自定义注解进行配置DemoService

package com.suzao.demo.service;

import com.suzao.mvcframework.annotation.SZService;

/**

* @ClassName DemoService

* @Description: TODO

* @Author mc

* @Date 2020

* @Version V1.0

**/

@SZService

public class DemoService implements IDemoService {

@Override

public String get(String name) {

return "My name is "+ name;

}

}

12、IDemoService

package com.suzao.demo.service;

/**

* @ClassName IDemoService

* @Description: TODO

* @Author mc

* @Date 2020

* @Version V1.0

**/

public interface IDemoService {

String get(String name);

}

二、初始化阶段

1、先在 SZDispatcherServlet 中声明几个成员变量

//保存application.properties配置文件中的内容

private Properties contextConfig = new Properties();

//保存扫描的所有的类名

private List classNames = new ArrayList<>();

//IOC容器

private Map ioc = new HashMap<>();

//保存url和Method的对应关系

private Map handleMapping = new HashMap<>();

2、SZDispatcherServlet 的init()方法

当 Servlet 容器启动时,会调用 SZDispatcherServlet 的 init()方法,从 init 方法的参数中,我们可以拿到主配置文件的路径,从能够读取到配置文件中的信息。前面我们已经介绍了 Spring 的三个阶段,现在来完成初始化阶段的代码。在 init() 方法中,定义好执行步骤,如下:

@Override

public void init(ServletConfig config) throws ServletException {

//1.加载配置文件

doLoadConfig(config.getInitParameter("contextConfigLocation"));

//2.扫描相关的类

doScanner(contextConfig.getProperty("scanPackage"));

//3.初始化扫描到的类,并且放入到IOC容器中

doInstance();

//4.完成自动化的依赖注入

doAutowired();

//5.初始化HandlerMapping

doInitHandlerMapping();

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

}

3、doLoadConfig() 方法

doLoadConfig() 方法的实现,将文件读取到 Properties 对象中

//加载配置文件

private void doLoadConfig(String contextConfigLocation) {

//直接从类路径下找到Spring主配置文件所在的路径

//并且将其读取出来放到Properties对象中

//相对于scanPackage=com.suzao.demo从文件中保存到内存中

InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

try {

contextConfig.load(is);

} catch (IOException e) {

e.printStackTrace();

}finally {

try {

is.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

4、doScanner() 方法

doScanner() 方法,递归扫描出所有的 Class 文件

//扫描出相关的类

private void doScanner(String scanPackage) {

URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.","/" ));

//scanPackage= com.suzao.demo 存储的包路径

//转换为文件路径,实际上就是把 .替换成/

//classpath下不仅有.class 文件, .xml文件 .properties文件

File classPath = new File(url.getFile());

for (File file :classPath.listFiles()){

if(file.isDirectory()){

doScanner(scanPackage + "." + file.getName());

}else {

//变成包名.类名

//Class.forname()

if(!file.getName().endsWith(".class")){

continue;

}

classNames.add(scanPackage + "." + file.getName().replace(".class","" ));

}

}

}

5、doInstance() 方法

doInstance() 方法,初始化所有相关的类,并放入到 IOC 容器之中。IOC 容器的 key 默认是类名首字母小写,如果是自己设置类名,则优先使用自定义的。因此,要先写一个针对类名首字母处理的工具方法

private String toLowerFirstCase(String simpleName) {

char[] chars = simpleName.toCharArray();

chars[0] += 32;

return String.valueOf(chars);

}

private void doInstance() {

if(classNames.isEmpty()){

return;

}

try {

for(String className : classNames){

Class> clazz = Class.forName(className);

//什么样的类才需要初始化呢

//加了注解的类,才初始化,怎么判断

//为了简化代码逻辑,主要体会设计思想,只举例@Controller @Service

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

Object instance = clazz.newInstance();

String beanName = toLowerFirstCase(clazz.getSimpleName());

ioc.put(beanName,instance );

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

//1.默认根据beanName类名首字母小写

String beanName = toLowerFirstCase(clazz.getSimpleName());

//2.使用自定义的beanName

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

if(!"".equals(service.value())){

beanName = service.value();

}

Object instance = clazz.newInstance();

ioc.put(beanName, instance);

//3.根据包名.类名作为beanName

for (Class> i : clazz.getInterfaces()){

if(ioc.containsKey(i.getName())){

throw new Exception("The beanName is exists!!");

}

//把接口的类型直接当成key了

ioc.put(i.getName(),instance );

}

}else {

continue;

}

}

}catch (Exception e){

e.printStackTrace();

}

}

doAutowired() 方法

doAutowired() 方法,将初始化到 IOC 容器中的类,需要赋值的字段进行赋值

private void doAutowired() {

if(ioc.isEmpty()){

return;

}

for (Map.Entry entry : ioc.entrySet()){

Field[] fields = entry.getValue().getClass().getDeclaredFields();

for (Field field : fields){

if(!field.isAnnotationPresent(SZAutowired.class)){

continue;

}

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

//如果用户没有自定义beanname ,默认就根据类型注入

String beanName = autowired.value().trim();

if("".equals(beanName)){

beanName = field.getType().getName();

}

//如果是public以外的修饰符,只要加了@Autowired注解,都要强制赋值

//暴力访问

field.setAccessible(true);

//反射调用

//给entry.getValue()这个对象的field字段,赋ioc.get(beanName)的值

try {

field.set(entry.getValue(),ioc.get(beanName) );

} catch (IllegalAccessException e) {

e.printStackTrace();

continue;

}

}

}

}

6、doInitHandlerMapping() 方法

doInitHandlerMapping() 方法,将 SZRequestMapping 中配置的信息和 Method 进行关联,并保存这些关系。

//初始化url和method的一对一对应关系

private void doInitHandlerMapping() {

if(ioc.isEmpty()){

return;

}

for (Map.Entry entry : ioc.entrySet()){

Class> clazz = entry.getValue().getClass();

if(!clazz.isAnnotationPresent(SZController.class)){

continue;

}

//保存写在类上面的@GPRequestMapping("/demo")

String baseUrl = "";

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

SZRequestMapping requestMapping = clazz.getAnnotation(SZRequestMapping.class);

baseUrl = requestMapping.value();

}

//默认获取所有的public方法

for(Method method : clazz.getMethods()){

if(!method.isAnnotationPresent(SZRequestMapping.class)){

continue;

}

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

String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+","/" );

handleMapping.put(url,method );

System.out.println("Mapped " + url + "," + method);

}

}

}

到此,初始化阶段的所有代码全部写完。

三、运行阶段

1、doPost()方法

来到运行阶段,当用户发送请求被 Servlet 接受时,都会统一调用 doPost 方法,我先在 doPost 方法中再调用 doDispach() 方法

@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){

e.printStackTrace();

resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace()));

}

}

2、doDispatch() 方法

private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{

String url = req.getRequestURI();

String contextPath = req.getContextPath();

url = url.replace(contextPath,"" ).replaceAll("/+","/" );

if(!this.handleMapping.containsKey(url)){

resp.getWriter().write("404 Not Found!");

return;

}

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

Map paramsMap = req.getParameterMap();

//实参列表要根据形参列表才能决定,首先得拿到形参列表

Class>[] parameterTypes = method.getParameterTypes();

Object[] parameValus = new Object[parameterTypes.length];

for (int i = 0 ;i< parameterTypes.length ; i++){

Class paramterType = parameterTypes[i];

if(paramterType == HttpServletRequest.class){

parameValus[i] = req;

continue;

}else if(paramterType == HttpServletResponse.class){

parameValus[i] = resp;

continue;

}else if(paramterType == String.class){

Annotation[][] pa = method.getParameterAnnotations();

for (Annotation a : pa[i]){

if(a instanceof SZRequestParam){

String paramName = ((SZRequestParam) a).value();

if(!"".equals(paramName.trim())){

String value = Arrays.toString(paramsMap.get(paramName))

.replaceAll("\\[|\\]", "")

.replaceAll("\\s","," );

parameValus[i] = value;

}

}

}

/*for(int j =0 ;j < pa.length ;j++){

}*/

}

}

String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());

method.invoke(ioc.get(beanName),parameValus);

}

3eb9a656c86b

image.png

当然,真正的 Spring 要复杂很多,但核心设计思路基本如此。

4、SZDispatchServlet完整代码

package com.suzao.mvcframework.servlet.v2;

import com.suzao.mvcframework.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.Method;

import java.net.URL;

import java.util.*;

/**

* @ClassName SZDispatchServlet

* @Description: TODO

* @Author mc

* @Date 2020

* @Version V1.0

**/

public class SZDispatchServlet extends HttpServlet {

//保存application.properties配置文件中的内容

private Properties contextConfig = new Properties();

//保存扫描的所有的类名

private List classNames = new ArrayList<>();

//IOC容器

private Map ioc = new HashMap<>();

//保存url和Method的对应关系

private Map handleMapping = 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){

e.printStackTrace();

resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace()));

}

}

private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{

String url = req.getRequestURI();

String contextPath = req.getContextPath();

url = url.replace(contextPath,"" ).replaceAll("/+","/" );

if(!this.handleMapping.containsKey(url)){

resp.getWriter().write("404 Not Found!");

return;

}

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

Map paramsMap = req.getParameterMap();

//实参列表要根据形参列表才能决定,首先得拿到形参列表

Class>[] parameterTypes = method.getParameterTypes();

Object[] parameValus = new Object[parameterTypes.length];

for (int i = 0 ;i< parameterTypes.length ; i++){

Class paramterType = parameterTypes[i];

if(paramterType == HttpServletRequest.class){

parameValus[i] = req;

continue;

}else if(paramterType == HttpServletResponse.class){

parameValus[i] = resp;

continue;

}else if(paramterType == String.class){

Annotation[][] pa = method.getParameterAnnotations();

for (Annotation a : pa[i]){

if(a instanceof SZRequestParam){

String paramName = ((SZRequestParam) a).value();

if(!"".equals(paramName.trim())){

String value = Arrays.toString(paramsMap.get(paramName))

.replaceAll("\\[|\\]", "")

.replaceAll("\\s","," );

parameValus[i] = value;

}

}

}

/*for(int j =0 ;j < pa.length ;j++){

}*/

}

}

String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());

method.invoke(ioc.get(beanName),parameValus);

}

@Override

public void init(ServletConfig config) throws ServletException {

//1.加载配置文件

doLoadConfig(config.getInitParameter("contextConfigLocation"));

//2.扫描相关的类

doScanner(contextConfig.getProperty("scanPackage"));

//3.初始化扫描到的类,并且放入到IOC容器中

doInstance();

//4.完成自动化的依赖注入

doAutowired();

//5.初始化HandlerMapping

doInitHandlerMapping();

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

}

//初始化url和method的一对一对应关系

private void doInitHandlerMapping() {

if(ioc.isEmpty()){

return;

}

for (Map.Entry entry : ioc.entrySet()){

Class> clazz = entry.getValue().getClass();

if(!clazz.isAnnotationPresent(SZController.class)){

continue;

}

//保存写在类上面的@GPRequestMapping("/demo")

String baseUrl = "";

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

SZRequestMapping requestMapping = clazz.getAnnotation(SZRequestMapping.class);

baseUrl = requestMapping.value();

}

//默认获取所有的public方法

for(Method method : clazz.getMethods()){

if(!method.isAnnotationPresent(SZRequestMapping.class)){

continue;

}

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

String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+","/" );

handleMapping.put(url,method );

System.out.println("Mapped " + url + "," + method);

}

}

}

private void doAutowired() {

if(ioc.isEmpty()){

return;

}

for (Map.Entry entry : ioc.entrySet()){

Field[] fields = entry.getValue().getClass().getDeclaredFields();

for (Field field : fields){

if(!field.isAnnotationPresent(SZAutowired.class)){

continue;

}

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

//如果用户没有自定义beanname ,默认就根据类型注入

String beanName = autowired.value().trim();

if("".equals(beanName)){

beanName = field.getType().getName();

}

//如果是public以外的修饰符,只要加了@Autowired注解,都要强制赋值

//暴力访问

field.setAccessible(true);

//反射调用

//给entry.getValue()这个对象的field字段,赋ioc.get(beanName)的值

try {

field.set(entry.getValue(),ioc.get(beanName) );

} catch (IllegalAccessException e) {

e.printStackTrace();

continue;

}

}

}

}

private void doInstance() {

if(classNames.isEmpty()){

return;

}

try {

for(String className : classNames){

Class> clazz = Class.forName(className);

//什么样的类才需要初始化呢

//加了注解的类,才初始化,怎么判断

//为了简化代码逻辑,主要体会设计思想,只举例@Controller @Service

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

Object instance = clazz.newInstance();

String beanName = toLowerFirstCase(clazz.getSimpleName());

ioc.put(beanName,instance );

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

//1.默认根据beanName类名首字母小写

String beanName = toLowerFirstCase(clazz.getSimpleName());

//2.使用自定义的beanName

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

if(!"".equals(service.value())){

beanName = service.value();

}

Object instance = clazz.newInstance();

ioc.put(beanName, instance);

//3.根据包名.类名作为beanName

for (Class> i : clazz.getInterfaces()){

if(ioc.containsKey(i.getName())){

throw new Exception("The beanName is exists!!");

}

//把接口的类型直接当成key了

ioc.put(i.getName(),instance );

}

}else {

continue;

}

}

}catch (Exception e){

e.printStackTrace();

}

}

private String toLowerFirstCase(String simpleName) {

char[] chars = simpleName.toCharArray();

chars[0] += 32;

return String.valueOf(chars);

}

//扫描出相关的类

private void doScanner(String scanPackage) {

URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.","/" ));

//scanPackage= com.suzao.demo 存储的包路径

//转换为文件路径,实际上就是把 .替换成/

//classpath下不仅有.class 文件, .xml文件 .properties文件

File classPath = new File(url.getFile());

for (File file :classPath.listFiles()){

if(file.isDirectory()){

doScanner(scanPackage + "." + file.getName());

}else {

//变成包名.类名

//Class.forname()

if(!file.getName().endsWith(".class")){

continue;

}

classNames.add(scanPackage + "." + file.getName().replace(".class","" ));

}

}

}

//加载配置文件

private void doLoadConfig(String contextConfigLocation) {

//直接从类路径下找到Spring主配置文件所在的路径

//并且将其读取出来放到Properties对象中

//相对于scanPackage=com.suzao.demo从文件中保存到内存中

InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

try {

contextConfig.load(is);

} catch (IOException e) {

e.printStackTrace();

}finally {

try {

is.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值