使用java簡單實現的一個Spring框架(pom.xml僅包含Servlet jar包依賴)

開始前的準備

開發環境:jdk8+tomcat7+IDEA+maven
所需jar包:Servlet2.x
那麼現在就開始吧


開發過程(絕對詳細)

首先,啓動IDEA創建一個maven項目,並創建下圖所示的包結構,包名你們自己起就可以了

這裏寫圖片描述

然後配置pom.xml,在裏面引入Servlet依賴就可以了,這裏我引入的2.x的,你們不要引入3.0的,雖然3.0的可以不需要web.xml配置,不過這裏需要加載配置文件的
</dependency>
      <!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
      <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>servlet-api</artifactId>
          <version>2.5</version>
          <scope>provided</scope>
      </dependency>
在Action中創建一個類,這裏我創建的類爲DemoAction.java,該類的作用在SSM框架中等同於Controller類,在裏面寫入下面代碼:
@Controller("demoAction")
@RequestMapping("/web")
public class DemoAction {

    @Autowired("tom")
    private IDemoService demoService;

    @RequestMapping("/query")
    public void query(HttpServletRequest request, HttpServletResponse response,
                      @RequestParam("name") String name) throws IOException {

        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");

        String result = demoService.get(name);

        PrintWriter writer = response.getWriter();
        writer.print(result);
        writer.close();
    }

}
這裏要注意一點,裏面我所用到的註解都是需要自己實現的,而不是Spring框架中的
然後我們來將所有類一個個的寫上,首先先寫Service吧,IDemoService是一個接口類,代碼如下:
public interface IDemoService {
    public String get(String name);
}
它有一個實現類DemoService:
@Service("tom")
public class DemoService implements IDemoService {

    public String get(String name){
        return "my name is " + name;
    }
}
到這裏,可以看到前面已經使用了很多的註解,但是都是報錯的,所以現在我們來一個個的實現這些註解吧,很簡單,我就直接貼到下面了:
Controller.java
//在類上可以使用
@Target({ElementType.TYPE})
//在運行時起作用
@Retention(RetentionPolicy.RUNTIME)
//讓它可以識別
@Documented
public @interface Controller {

    String value() default "";
}

RequestMapping.java
//在類和方法上可以使用
@Target({ElementType.TYPE, ElementType.METHOD})
//在運行時起作用
@Retention(RetentionPolicy.RUNTIME)
//讓它可以識別
@Documented
public @interface RequestMapping {

    String value() default "";
}

RequestParam.java
//在參數上可以使用
@Target({ElementType.PARAMETER})
//在運行時起作用
@Retention(RetentionPolicy.RUNTIME)
//讓它可以識別
@Documented
public @interface RequestParam {

    String value() default "";
}

Autowired.java
//在屬性上可以使用
@Target({ElementType.FIELD})
//在運行時起作用
@Retention(RetentionPolicy.RUNTIME)
//讓它可以識別
@Documented
public @interface Autowired {

    String value() default "";
}

Service.java
//在類上可以使用
@Target({ElementType.TYPE})
//在運行時起作用
@Retention(RetentionPolicy.RUNTIME)
//讓它可以識別
@Documented
public @interface Service {

    String value() default "";
}
到這裏爲止,所有的代碼都沒有報錯了,這個時候我們來配置web.xml,同Spring,使用的時候需要配置一個核心Servlet類:DispatcherServlet,先暫時不管有沒有,先按照Spring的方式來配置:
    <servlet>
        <servlet-name>mymvc</servlet-name>
        <servlet-class>org.chengxi.mvnframework.servlet.DispatcherServlet</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>mymvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
在配置中我給這個DispatcherServlet初始化了一個參數contextConfigLocation,它的值是屬性文件的位置,application.properties和applicationContext.xml功能一樣,提供Spring所需的一些屬性值,這裏就簡單的使用鍵值對的形式進行解析而不解析XML了。先來編寫這個屬性文件吧,裏面很簡單,就一個鍵值對,表示需要進行IOC/DI的類所在的包:
//application.properties
scanPackage = org.chengxi.demo
該配置文件需要放在main/resources下面;然後接下來就是真正的關鍵實現了:DispatcherServlet的實現,它就是一個Servlet,先將它的基礎打起來:
public class DispatcherServlet extends HttpServlet{
    public void init(){}
    public void doGet(){}
    public void doPost(){}
    public void destroy(){}
}
然後我們來一步步的實現,首先我們需要在init初始化階段獲取屬性文件對應的位置,即在web.xml中初始化的ContextConfigLocation變量,通過ServletConfig來獲取:
    String application = config.getInitParameter("contextConfigLocation");
在獲取了屬性文件的位置之後,就來解析該屬性文件裏的值,在這裏也就是獲取需要進行IOC/DI的類的包:
    InputStream is = this.getClass().getClassLoader().
                getResourceAsStream(location);
        p.load(is);
        if(null != is){
            is.close();
        }
這裏的p定義成一個成員變量,以便於後面使用,獲取了Properties對象之後,就可以獲取需要掃描的包,然後將所有的包進行掃描,並將所有的類都保存起來:
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.","/"));
        File dir = new File(url.getFile());

        for(File f: dir.listFiles()){
            if(f.isDirectory()){
                doScanner(packageName + "." + f.getName());
            }
            else{
                classNames.add(packageName + "." + f.getName().replace(".class","").trim());
            }
        }
這裏的classNames是一個List,用於保存指定包下的所有的類的位置
接下來,就需要我們來將該包下的所有Controller註解和Service註解修飾的類都進行實例化,並一一與beanName進行對應(這裏我們需要知道,Spring中維護這一個IOC容器,依賴注入就是通過該容器根據beanName進行控制反轉獲取來進行對應實例化的),ioc容器的定義:private Map<String, Object> ioc = new HashMap<String, Object>();
if(classNames.isEmpty()){
            return ;
        }
        for(String className: classNames){

            Class<?> clazz = Class.forName(className);
            //只初始化controller容器和service容器
            if(clazz.isAnnotationPresent(Controller.class)){

                //默認首字母小寫稱爲beanName
                String beanName = lowerFirst(clazz.getSimpleName());
                ioc.put(beanName, clazz.newInstance());
            }
            else if(clazz.isAnnotationPresent(Service.class)){

                //第一種形式:默認首字母小寫
                //第二種形式:註解自己提供名字(優先)
                //第三種形式:利用接口本身全稱作爲key,其對應實現類作爲值

                Service service = clazz.getAnnotation(Service.class);
                String beanName = service.value();

                Object instance = clazz.newInstance();
                //註解本身提供名字@service("sss")
                if(!"".equals(beanName.trim())){
                    ioc.put(beanName, instance);
                    continue;
                }

                Class<?>[] interfaces = clazz.getInterfaces();
                for(Class<?> inter: interfaces){
                    ioc.put(inter.getName(), instance);
                }
            }
        }
然後將使用Autowired註解修飾了的屬性進行依賴注入:
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(Autowired.class)){
                    continue;
                }
                Autowired autowired = field.getAnnotation(Autowired.class);

                String beanName = autowired.value().trim();
                if("".equals(beanName)){
                    beanName = field.getType().getName();
                }

                //即使是private也要進行強制注入
                field.setAccessible(true);

                //開始賦值
                field.set(entry.getValue(), ioc.get(beanName));
            }
        }
到這裏的時候,就差最後一步了:url與method進行對應,雖然這裏可以使用Map<String,Method>進行保存對應,但是需要注意的是,到後面進行method.invoke的時候需要的參數就無法獲得了,所以這裏我們定義一個外部類:
class Handler{

    //保存方法對應的實例
    protected Object controller;

    //保存映射的方法
    protected Method method;

    protected Pattern pattern;

    //保存方法的參數順序
    protected Map<String, Integer> paramIndexMapping;

    protected Handler(Object controller, Method method, Pattern pattern){

        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        paramIndexMapping = new HashMap<String, Integer>();
        putParamIndexMapping(method);
    }

    //提取方法中加了@RequestParam註解的參數
    public void putParamIndexMapping(Method method){

        //提取方法中加了註解的參數
        Annotation[][] pa = method.getParameterAnnotations();
        for(int i=0; i<pa.length; i++){
            for(Annotation a: pa[i]){
                if(a instanceof RequestParam){
                    String paramName = ((RequestParam) a).value();
                    if(!"".equals(paramName)){
                        paramIndexMapping.put(paramName,i);
                    }
                }
            }
        }

        //提取方法中的resp和req參數
        Class<?>[] paramsTypes = method.getParameterTypes();
        for(int i=0; i<paramsTypes.length; i++){
            Class<?> type = paramsTypes[i];
            if(type == HttpServletRequest.class || type == HttpServletResponse.class){
                paramIndexMapping.put(type.getName(), i);
            }
        }
    }
}
然後我們在DispatcherServlet中直接定義一個List<HandlerMapping>進行保存即可,進行保存的方法如下:
if(ioc.isEmpty()){
            return ;
        }
        for(Map.Entry<String, Object> entry: ioc.entrySet()){
            Class<?> clazz = entry.getValue().getClass();

            if(!clazz.isAnnotationPresent(Controller.class)){
                continue;
            }
            String url = "";
            if(clazz.isAnnotationPresent(RequestMapping.class)){
               RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
               url = requestMapping.value();
            }

            Method[] methods = clazz.getMethods();
            for(Method method: methods){
                if(!method.isAnnotationPresent(RequestMapping.class)){
                    continue;
                }

                RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                String regex = ("/" + url + requestMapping.value()).replaceAll("/+","/");
                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(entry.getValue(), method, pattern));
            }
        }
最後一步:請求的處理,這裏定義一個方法來進行處理:doDispatcherServlet(req, resp),處理過程如下:
Handler handler = getHandler(req);
        if(handler == null){
            resp.getWriter().print("404 not found");
            return;
        }

        //獲取方法的參數列表
        Class<?>[] paramTypes = handler.method.getParameterTypes();

        //保存所有需要自動賦值的參數值
        Object[] paramValues = new Object[paramTypes.length];

        Map<String, String[]> params = req.getParameterMap();
        for(Map.Entry<String, String[]> param: params.entrySet()){
            String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","").replaceAll(",\\s",",");

            if(!handler.paramIndexMapping.containsKey(param.getKey())){
                continue;
            }
            int index = handler.paramIndexMapping.get(param.getKey());
            paramValues[index] = convert(paramTypes[index], value);
        }

        //設置方法中的request和response對象
        int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
        paramValues[reqIndex] = req;
        int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
        paramValues[respIndex] = resp;

        handler.method.invoke(handler.controller, paramValues);
其中裏面用到了幾個方法,定義在下面,就不一一介紹了,這幾個方法還是很好理解的:
public Object convert(Class<?> type, String value){
        if(Integer.class == type){
            return Integer.valueOf(value);
        }
        return value;
    }

    public Handler getHandler(HttpServletRequest req){

        if(handlerMapping.isEmpty()){
            return null;
        }

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+","/");

        for(Handler handler: handlerMapping) {
            Matcher matcher = handler.pattern.matcher(url);
            if(!matcher.matches()){
                continue;
            }
            return handler;
        }
        return null;
    }

    //首字母小寫
    private String lowerFirst(String str){
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }


最後總結

寫這篇博客主要是用於自己記錄的,大致的實現應該介紹的比較詳細了,代碼裏面也做了很多的筆記,你們可以多看看。我已經將代碼上傳到了CSDN上,你們可以下載邊看邊學,地址:mymvc
發佈了207 篇原創文章 · 獲贊 75 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章