使用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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章