手寫簡易的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就誕生了!
    在這裏插入圖片描述

源碼地址

點擊查看

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