Spring框架进阶Spring V1.0

Posted 烟锁迷城

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架进阶Spring V1.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 

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

以上是关于Spring框架进阶Spring V1.0的主要内容,如果未能解决你的问题,请参考以下文章

SSH进阶之路一步步重构容器实现Spring框架——彻底封装,实现简单灵活的Spring框架

Spring框架进阶3

Spring框架进阶Spring V2.0 AOP

Spring框架进阶Spring V2.0 MVC

Spring框架进阶常见组件

Spring框架进阶Spring V3.0 DI源码分析流程