手写简易的spring框架

首先感谢腾讯课堂【咕泡学院】的视频,本文代码也是按照此视频进行编写及一些修改完成!

创建项目

首先,创建一个maven项目,使用jre1.8,目录结构如下
在这里插入图片描述

1.插入servlet-api

在pom.xml引入javax.servlet-api

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>

2.自定义注解

在spring中,注解的使用可以说是非常频繁的,更表明了它是非常有用的,而spring中更是拥有着众多注解,我们此次仅仅实现其中的4个注解.

在cn.jnx.spring.annotation包下新增四个注解

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

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

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

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

3.创建字符集过滤器

package cn.jnx.spring.servlet;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 
 * @ClassName:  CharacterEncodingFilter   
 * @Description 字符编码过滤器,默认采用utf-8
 * @version 
 * @author jh
 * @date 2020年5月27日 上午10:59:33
 */
public class CharacterEncodingFilter implements Filter {

    private String encoding = "UTF-8";

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        req.setCharacterEncoding(encoding);
        resp.setCharacterEncoding(encoding);
        resp.setContentType("text/html;charset=" + encoding);
        chain.doFilter(req, resp);
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // 获取指定字符集信息
        String initEncoding = arg0.getInitParameter("encoding");
        if (null != initEncoding) {
            this.encoding = initEncoding.trim().toUpperCase();
        }

    }


}


4.创建自定义dispatcherServlet

package cn.jnx.spring.servlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.jnx.spring.annotation.MyAutoWired;
import cn.jnx.spring.annotation.MyController;
import cn.jnx.spring.annotation.MyRequestMapping;
import cn.jnx.spring.annotation.MyService;

public class DispatcherServlet extends HttpServlet {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    // 配置文件
    private Properties contextConfig = new Properties();
    // 储存扫描的class文件路径,包名加类名(cn.jnx.annotation.MyAutoWired)
    private List<String> classNames = new ArrayList<>();
    // ioc容器
    private Map<String, Object> ioc = new HashMap<>();
    // url映射容器
    private Map<String, Method> handlerMapping = 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 Detail:" + Arrays.toString(e.getStackTrace()));
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        // 2.扫描配置文件
        doScanner(contextConfig.getProperty("scanPackage"));
        try {
            // 3.实例化相关类
            doInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 4.完成依赖注入
        doAutoWired();
        // 5.初始化 HandleMapping
        doInitHandlerMapping();
        System.out.println("MY-SPRING:启动完成!");

    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp)
            throws IOException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
        if (!handlerMapping.containsKey(url)) {
            throw new RuntimeException(url + "   404 Not Found!!");
//            resp.getWriter().write("404 Not Found!!");
        }
        Method method = this.handlerMapping.get(url);
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        method.invoke(ioc.get(beanName), new Object[] { req, resp });
    }

    private void doInitHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<? extends Object> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(MyController.class)) {
                return;
            }
            String baseUrl = "";
            if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.println("Mapped" + url + "," + method);
            }
        }
    }

    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) {
                if (!field.isAnnotationPresent(MyAutoWired.class)) {
                    return;
                }
                MyAutoWired myAutoWired = field.getAnnotation(MyAutoWired.class);
                // 获取自定义注解的值
                String beanName = myAutoWired.value().trim();
                if ("".equals(beanName)) {// 若自定义注解未设置值,则取类型名
                    beanName = field.getType().getName();
                }
                // 允许强制访问
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private void doInstance() throws Exception {
        if (classNames.isEmpty()) {
            return;
        }
        try {
            for (String className : classNames) {
                // forName()==>将.class文件加载到jvm内,并且对类进行解释,执行类中的static静态代码快
                Class<?> clazz = Class.forName(className);
                // 是否包含MyController注解
                if (clazz.isAnnotationPresent(MyController.class)) {
                    String beanName = toLowerFirstCase(clazz.getSimpleName());// 默认首字母小写类名
                    // 使用类名首字母小写作为key,创建该类的新的对象作为value,保存到ioc中
                    ioc.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(MyService.class)) {// 是否包含MyService注解
                    // 1.默认类名小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());// 默认首字母小写类名
                    // 2.自定义命名
                    MyService service = clazz.getAnnotation(MyService.class);
                    if (!"".equals(service.value())) {// 添加自定义命名
                        beanName = service.value();// 使用用户命名的name
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);
                    // 3.把service的接口实例化为实现类,实例化实现类
                    for (Class<?> i : clazz.getInterfaces()) {// 如果有实现类
                        if (ioc.containsKey(i.getName())) {
                            throw new Exception("The beanName is exists!");
                        }
                        ioc.put(i.getName(), instance);
                    }
                } else {
                    continue;
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

    // 首字母小写方法
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void doScanner(String scanPackage) {
        // 获取本地存放class文件目录
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replace(".", "/"));
        // file:/D:/apache-tomcat-windows-x64/apache-tomcat-8.5.37/webapps/my-spring/WEB-INF/classes/cn/jnx/
        // 获取声明需要扫描的class文件所声明的文件夹
        File classPath = new File(url.getFile().replace("%20", " "));//处理文件名空格
        // listFiles()==>返回某个目录下所有文件和目录的绝对路径,返回的是File数组
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {// 如果是文件夹,递归
                doScanner(scanPackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) {

        try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);) {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

5.打包

本文选择把核心代码打成jar包后,其他项目通过导入外部jar包方式引入,模拟引入spring的过程。

右键项目–选择Export
在这里插入图片描述
在这里插入图片描述

这里如果勾选了第4步,在引入项目后,双击打开.class文件后可以直接查看到java代码,并且打开jar包可也看到连带.java文件一同会打包进去
在这里插入图片描述在这里插入图片描述

未勾选状态,无法查看源码
在这里插入图片描述

进行测试

新建测试项目testspring

目录结构如下
在这里插入图片描述

  1. 把jar包放入lib文件夹内,没有可以自己新建一个
  2. 修改pom.xml文件,引入jar
    这里需要注意,自己打包的jar,插入maven项目需要这样插入,如果用插入外部jar的形式引入项目,只会在编译期间有效,运行时会找不到class。查看其他引入方法
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>cn.jnx</groupId>
	<artifactId>testspring</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>testspring Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<encoding>UTF-8</encoding>
		<java.version>1.8</java.version>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>
	<dependencies>
		<dependency>
			<groupId>cn.jnx</groupId>
			<artifactId>spring</artifactId>
			<version>1.0.0</version>
			<scope>system</scope>
			<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/spring.jar</systemPath>
		</dependency>
		<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>testspring</finalName>
	</build>
</project>
  1. 修改web.xml文件,启用我们自定义的spring
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">


	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>cn.jnx.spring.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<description></description>
			<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>dispatcher</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>

	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>cn.jnx.spring.servlet.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<display-name>Archetype Created Web Application</display-name>
</web-app>

  1. resource下新建application.properties文件,内容如下,需要对应测试项目需要扫描类的最上级路径
scanPackage=cn.jnx.test

在这里插入图片描述

  1. 然后就是controllerservice
  • TestController
@MyController
public class TestController {
    
    @MyAutoWired
    private TestService testService;
    @MyRequestMapping("/hello")
    public void hello(HttpServletRequest request,HttpServletResponse response) {
        testService.hello(request, response);
    }
}
  • TestService
public interface TestService {
    
    public void hello(HttpServletRequest request,HttpServletResponse response);
}

  • TestServiceImpl
@MyService
public class TestServiceImpl implements TestService {

    public void hello(HttpServletRequest request, HttpServletResponse response) {
        String name = request.getParameter("name");
        try {
            response.getWriter().write("Hello :" + name);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

  1. 部署到tomcat后并启动容器
    正确会在启动过程中显示启动完成字样。
Mapped/hello,public void cn.jnx.test.controller.TestController.hello(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
MY-SPRING:启动完成!
  1. 使用postmain进行测试
    在这里插入图片描述
  2. 我们可以修改一下自定义字符集来测试一下
    修改默认字符集为GBK
    在这里插入图片描述
    更改完成,至此,一个简单(充满bug)的spring就诞生了!
    在这里插入图片描述

源码地址

点击查看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章