Spring框架进阶(二)Spring V1.0

20 篇文章 1 订阅
11 篇文章 0 订阅

目录

1、基本思路

1.1、配置

1.2、初始化

1.3、运行

2、总体设计

2.1、自定义注解

2.2、配置文件

 2.3、自定义Servlet

3、具体实现

3.1、加载配置文件

3.2、扫描配置对应包

3.3、IOC的初始化与实例注入

3.4、依赖注入

3.5、URL与方法的匹配

3.6、具体调用实现


spring的主要功能是IOC,DI,MVC,AOP,如果一个系统能实现这些功能,就可以实现一个简易版本的Spring框架。

1、基本思路

Spring的实现可以分为三个基本的阶段,配置,初始化,运行。

1.1、配置

配置web.xml:DispatcherServlet,SpringMVC的入口,最初始的入口

设定init-param:主配置文件的接入,contextConfigLocation=classpath:application.xml

设定url-pattern:servlet的配置/*

配置Annotation:对应的各个层级注解,@Controller,@Service,@Component

1.2、初始化

调用init()方法:加载配置文件

IOC容器初始化:IOC容器本质还是一个Map

扫描类:会在配置文件中配置一个暴露路径

创建初始化实例并保存至容器:使用反射机制将类的实例放入IOC中

进行DI:扫描IOC实例,给没有赋值的属性赋值

初始化HandlerMapping:MVC结构部分,将URL和Method的对应关系映射到Map中

1.3、运行

调用doPost()/doGet():Web容器调用doPost()/doGet()方法,获得request/response

匹配HandlerMapping:从request对象获得用户输入的URL,从HandlerMapping获取对应method

反射调用method.invoker():反射调用方法并返回结果

response.getWrite().write():将返回结果输出到浏览器

2、总体设计

2.1、自定义注解

在设计自己的Spring框架之前,需要一些新的注解来区别Spring注解

这些注解就是最简单的自定义注解,不包含任何其他作用。

2.2、配置文件

web.xml文件的详细配置,有关mvc和配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">
	<display-name>Gupao Web Application</display-name>
	<servlet>
		<servlet-name>mymvc</servlet-name>
		<servlet-class>com.example.springwrite.myframework.v1.MyDispatchServlet</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>
</web-app>

application.properties文件详细配置

scanPackage=com.example.springwrite.demo

 2.3、自定义Servlet

重新定义 Servlet,重写init,dopost和doget方法。

public class MyDispatchServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
    }

}

在init方法中,需要依照步骤实现

  1. 加载配置文件
  2. 根据配置文件扫描对应包下文件,获取到对应的类名列表
  3. 初始化IOC,根据类名列表进行实例化,将类放入IOC
  4. 对IOC容器内的bean进行DI
  5. 对URL和Method进行匹配
  6. 对请求进行方法实现

3、具体实现

3.1、加载配置文件

    private Properties contextConfig = new Properties();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
    }

第一个方法,对配置文件进行读取,指定读取对应的contextConfigLocation,读取方式为流,将读取到的配置文件数据保存到属性Properties

    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (resourceAsStream != null) {
                try {
                    resourceAsStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.2、扫描配置对应包

    private Properties contextConfig = new Properties();
    private List<String> classNames = new ArrayList<String>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2、扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));
    }

获取到包地址之后,需要进行一次转化,因为得到的地址是以“.”作为分隔符的,需要转化为“/”才能被识别。

获取到包下的文件列表后,需要进行循环处理,如果扫描到的是文件夹,就需要进行递归扫描,继续下一层级的搜索。

如果不是文件夹,就需要查看文件是否为class类型的文件,如果是class文件,就提取出class的名,将扫描包名增加在class名之前,组成可以被反射调用到的地址

    private void doScanner(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classpath = new File(url.getFile());
        for (File file : classpath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String fileName = file.getName().replace(".class", "");
                String className = scanPackage + "." + fileName;
                classNames.add(className);
            }
        }
    }

3.3、IOC的初始化与实例注入

    private Properties contextConfig = new Properties();
    private List<String> classNames = new ArrayList<String>();
    private Map<String, Object> ioc = new HashMap<String, Object>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2、扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3、初始化IOC容器,将扫描到的类进行实例化,缓存到IOC容器中
        doInstance();
    }

对获取到的文件名进行循环,然后进行实例化,将具有@MyController和@MyService等需要被IOC装载的类读取到IOC中。

对于有@MyController注解的类,按照Spring的默认规则,以类名首字母小写作为IOC的bean名称即可

对于有@MyService注解的类,如果没有别名,按照Spring的默认规则,以类名首字母小写作为IOC的bean名称即可。如果具有别名,则以别名作为bean名称,如果是一个接口,则将全名作为Bean名称。

    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }
        try {
            for (String className : classNames) {
                Class clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(MyController.class)) {
                    Object instance = clazz.newInstance();
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    Object instance = clazz.newInstance();
                    //1、首字母小写的类名
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    //2、同名类使用别名
                    MyService myService = instance.getClass().getAnnotation(MyService.class);
                    if (!"".equals(myService.value())) {
                        beanName = myService.value();
                    }
                    ioc.put(beanName, instance);
                    //3、如果是接口,就只能初始化实现类
                    for (Class claz : clazz.getInterfaces()) {
                        if (ioc.containsKey(claz.getName())) {
                            throw new Exception("接口多实现错误,清使用别名");
                        }
                        ioc.put(claz.getName(), instance);
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

3.4、依赖注入

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2、扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3、初始化IOC容器,将扫描到的类进行实例化,缓存到IOC容器中
        doInstance();
        //4、完成依赖注入
        doAutowired();
    }

被@MyAutowired注解修饰的属性无论是否具有私有修饰,都要被强制注入。在没有别名的情况下,用首字母小写的类名在IOC中寻找,如果有别名,按照别名进行寻找。

在注入私有属性时,反射需要允许强制访问。

    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> instance : ioc.entrySet()) {
            //忽略字段的修饰符
            for (Field field : instance.getValue().getClass().getDeclaredFields()) {
                if (!field.isAnnotationPresent(MyAutowired.class)) {
                    continue;
                }
                MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
                String beanName = myAutowired.value();
                if ("".equals(beanName)) {
                    beanName = field.getType().getName();
                }
                //私有变量需要开启反射强制访问
                field.setAccessible(true);
                try {
                    field.set(ioc.get(toLowerFirstCase(field.getDeclaringClass().getSimpleName())), ioc.get(beanName));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.5、URL与方法的匹配

    private Map<String, Method> handlerMapping = new HashMap<String, Method>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2、扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3、初始化IOC容器,将扫描到的类进行实例化,缓存到IOC容器中
        doInstance();
        //4、完成依赖注入
        doAutowired();
        //5、初始化HandlerMapping
        doInitHandlerMapping();
    }

查看被 @MyController的类是否也被@MyRequestMapping修饰,如果有,就在url前增加前缀

将被@MyController注解修饰的类中的公有方法取出,将没有被@MyRequestMapping修饰的方法排除,获取到对应的URL后,拼接,加入到HandlerMapping中,完成映射

    private void doInitHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> instance : ioc.entrySet()) {
            Class clazz = instance.getValue().getClass();
            String url = "";
            if (!clazz.isAnnotationPresent(MyController.class)) {
                continue;
            }
            if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping myRequestMapping = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class);
                url = "/" + myRequestMapping.value();
            }
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
                handlerMapping.put((url + "/" + myRequestMapping.value()).replaceAll("/+", "/"), method);
            }
        }
    }

3.6、具体调用实现

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6、根据URL委派给具体的调用方法
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 exception");
        }
    }

从HttpServletRequest中获取url和classpath,去除重复的“//”,从其中获取到参数列表和对应的参数注解,从这两者的匹配中获取到一个完整的参数列表,再用反射调用方法。

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 not found");
            return;
        }
        Method method = handlerMapping.get(url);
        Map<String, Integer> paramMapping = new HashMap<String, Integer>();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                if (annotation instanceof MyRequestParam) {
                    String value = ((MyRequestParam) annotation).value();
                    if (!"".equals(value)) {
                        paramMapping.put(value, i);
                    }
                }
            }
        }
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            if (HttpServletRequest.class == parameterType || HttpServletResponse.class == parameterType) {
                paramMapping.put(parameterType.getName(), i);
            }
        }
        Object[] paramValues = new Object[paramMapping.size()];
        Map<String, String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> param : params.entrySet()) {
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", "");
            if (!paramMapping.containsKey(param.getKey())) {
                continue;
            }
            paramValues[paramMapping.get(param.getKey())] = value;
        }

        if (paramMapping.containsKey(HttpServletRequest.class.getName())) {
            int index = paramMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = req;
        }

        if (paramMapping.containsKey(HttpServletResponse.class.getName())) {
            int index = paramMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }

        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        Object obj = ioc.get(beanName);
        method.invoke(obj, paramValues);
    }

最后启动代码,访问一个固定的url:http://localhost:8080/user/name?name=lily 

如果一切正常,将得到一个完整的返回结果。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值