Spring框架进阶Spring V1.0
Posted 烟锁迷城
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架进阶Spring V1.0相关的知识,希望对你有一定的参考价值。
目录
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方法中,需要依照步骤实现
- 加载配置文件
- 根据配置文件扫描对应包下文件,获取到对应的类名列表
- 初始化IOC,根据类名列表进行实例化,将类放入IOC
- 对IOC容器内的bean进行DI
- 对URL和Method进行匹配
- 对请求进行方法实现
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的主要内容,如果未能解决你的问题,请参考以下文章
从 0 开始手写一个 Spring MVC 框架,向高手进阶!
SSH进阶之路一步步重构容器实现Spring框架——彻底封装,实现简单灵活的Spring框架