手寫Spring框架,加深對Spring工做機制的理解!

【北京】 IT技術人員面對面試、跳槽、升職等問題,如何快速成長,得到大廠入門資格和升職加薪的籌碼?與大廠技術大牛面對面交流,解答你的疑惑。《從職場小白到技術總監成長之路:個人職場焦慮與救贖》活動連接: 碼客

恭喜fpx,新王登基,lpl*b 咱們是冠軍java

在咱們的平常工做中,常常會用到Spring、Spring Boot、Spring Cloud、Struts、Mybatis、Hibernate等開源框架,有了這些框架的誕生,平時的開發工做量也是變得愈來愈輕鬆,咱們用 Spring Boot 分分鐘能夠新建一個Web項目。git

記得本身剛開始工做的時候仍是在用ServletWeb項目,本身寫數據庫鏈接池,用原生JDBC操做數據庫,好了不發散了。回到這篇文章的主題,今天經過手寫Spring框架,幫你們深刻了解一下Spring的工做機制,文中涉及的代碼只用來幫助你們理解Spring,不會在線上使用,有不嚴謹的地方還請你們掠過。web

項目結構

file

框架部分實現

  1. 爲了區分框架部分代碼和業務部分代碼,咱們將這兩部分分別劃分在不一樣的包內 com.mars.democom.mars.framework,以便隨後只掃描業務代碼。
  2. 這裏是本身手寫Spring框架,因此不會引入任何Spring項目相關的包。
  3. 因爲是一個Web項目,全部咱們須要引入 servlet-api 包,僅供編譯器使用,全部配置 scopeprovided
新建一個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
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Spring Mvc Education</display-name>

    <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>
</web-app>
  1. 首先配置了一個 servlet, 名字是 marsmvc, 類全路徑是 com.mars.framework.servlet.MarsDispatcherServlet
  2. 設置了初始化參數名和值(這裏的值是整個項目的配置文件)。
  3. 配置 load-on-startup, 標記容器是否在啓動的時候就加載這個servlet(實例化並調用其init()方法)。
  4. 配置 servlet-mapping, 將全部請求轉發到這個servlet處理。
配置application.properties
scanPackage=com.mars.demo

這個比較好理解,僅配置了一項內容,意思是要掃描的包,隨後咱們會獲取這個值去加載容器。spring

定義咱們經常使用的註解
  1. MarsAutowired
  2. MarsController
  3. MarsRequestMapping
  4. MarsRequestParam
  5. 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功能

先列出框架在初始化的時候都要作那些事情編程

  1. 加載配置文件
  2. 掃描全部相關聯的類
  3. 初始化全部相關聯的類,而且將其保存在IOC容器裏面
  4. 執行依賴注入(把加了@Autowired註解的字段賦值)
  5. 構造HandlerMapping,將URL和Method進行關聯

接下來咱們一步步完成上面的操做api

@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中,因此添加相關插件以及配置:mvc

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>7.1.6.v20100715</version>
    <configuration>
        <stopPort>9988</stopPort>
        <stopKey>foo</stopKey>
        <scanIntervalSeconds>5</scanIntervalSeconds>
        <connectors>
            <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
                <port>8080</port>
                <maxIdleTime>60000</maxIdleTime>
            </connector>
        </connectors>
        <webAppConfig>
            <contextPath>/</contextPath>
        </webAppConfig>
    </configuration>
</plugin>

運行

file

點擊 jetty:run 運行項目

瀏覽器訪問: http://localhost:8080/demo/query?name=Mars

file

瀏覽器訪問:http://localhost:8080/demo/add?a=10&b=20
在這裏插入圖片描述
倉庫地址

(想自學習編程的小夥伴請搜索圈T社區,更多行業相關資訊更有行業相關免費視頻教程。徹底免費哦!)

相關文章
相關標籤/搜索