前言
本人第一次写博客,不太了解markdown的书写规范,如有不妥之处敬请谅解!本篇博客中主要针对Spring中的ioc功能进行简单的实现,具体的目录如下。
ioc容器介绍
在进行具体的设计之前,首先了解一下ioc的基本知识(已经掌握的朋友们可以跳过这一部分)。
容器(container)这个词感觉已经被滥用了,什么都是容器。容器顾名思义就是装东西的东西。例如水杯是一个容器,用来装水。IoC容器就是用来装应用中的对象实例。注意我强调的是对象,这意味着同一类型的对象你可以在IoC容器中放好几个。IoC容器中,IoC的意思就是Inversion of Control(控制反转)。首先明确一下这里的控制是控制什么?这里的控制范范的说就是控制对象的生命周期,明确到一个典型点来说就是对象的实例化,再具体一点来说就是将对象依赖的其它对象传递给该对象。在一般情况下,建立一个对象都是通过new,静态方法,工厂方法等等手段。springFramework采用了一种比较高效的机制,它将实例化好的对象全部存在一个类似map结构的容器当中,每一个key都对应一个实例化对象。
// Spring ioc容器
private Map<String,Object> ioc=new HashMap<String,Object>();
配置文件配置
在功能实现之前首先将开发环境配置好,笔者使用的开发环境为idea,工具jdk8+tomcat7,建立一个web项目,完成以下的配置准备工作。
web.xml文件
在这里需要配置好自定义的一个servlet,即hongSpringDispatcherServlet,url-pattern设置为/以此拦截所有请求
<?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/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>hongSpringDispatcherServlet</servlet-name>
<servlet-class>Spring.hongSpringDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>applicationHong.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>hongSpringDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
applicationHong.properties文件
该文件为模仿spring的配置文件,目前只包含了扫描器,确定扫描的包
scanPackage=Spring.mvc
自定义注解
自定义开发过程中所需要用到的注解hongAutoWired,hongController,hongRequestMapping,hongRequestParam,hongService
package Spring.annotation;
import java.lang.annotation.*;
//自动注入注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface hongAutoWired {
public String value() default "";
}
其他注解类似,在这里就不一一贴出来了,可以在下文的github仓库中下载源代码
controller测试Demo
package Spring.mvc.controller;
import Spring.annotation.hongAutoWired;
import Spring.annotation.hongController;
import Spring.annotation.hongRequestMapping;
import Spring.mvc.service.IDemoService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@hongController
@hongRequestMapping("/demo")
public class DemoController {
@hongAutoWired
IDemoService demoService;
@hongRequestMapping("/add")
public void add(HttpServletRequest request, HttpServletResponse response,String name) throws IOException {
// request.setCharacterEncoding("UTF-8");
name=new String(name.getBytes("ISO-8859-1"),"UTF-8");
String value=demoService.add(name);
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(value);
}
@hongRequestMapping("/delete")
public void delete(HttpServletRequest request, HttpServletResponse response,String name) throws IOException {
String value=demoService.delete(name);
response.getWriter().write(value);
}
@hongRequestMapping("/update")
public void update(HttpServletRequest request, HttpServletResponse response,String name) throws IOException {
String value=demoService.update(name);
response.getWriter().write(value);
}
}
service测试Demo
service接口
package Spring.mvc.service;
public interface IDemoService {
String add(String name);
String delete(String name);
String update(String name);
}
service具体的实现类
package Spring.mvc.service.serviceImpl;
import Spring.annotation.hongService;
import Spring.mvc.service.IDemoService;
@hongService
public class DemoService implements IDemoService {
@Override
public String add(String name) {
return "添加成功from service "+name;
}
@Override
public String delete(String name) {
return "删除成功from service "+name;
}
@Override
public String update(String name) {
return "更新成功from service "+name;
}
}
好了,前期的准备工作都已经完成,那么下面开始具体的实现
ioc容器具体实现
首先给出代码实现的流程图
对流程图进行大致的讲解,首先调用init()方法加载配置文件,即找到web.xml文件中contextConfigLocation所对应的文件的位置,然后是ioc容器的初始化,创建一个map对象。再扫描相关的类,找到scanPackage需要实例化的类,将扫描到的类通过反射进行实例化,并将得到的实例保存到ioc容器中。再遍历这些实例化的类,寻找是否有需要自动注入的类,有则完成字段的注入,最后一步将测试controller中的方法和程序访问的url进行绑定,保证能够在浏览器访问到该方法。描述比较简洁,下面来看具体的实现。
自定义servlet
首先自定义一个servlet类继承HttpServlet,下面是该类需要用到的容器。
private Properties contextConfig=new Properties();
private List<String> classNames=new ArrayList<String>();
private Map<String,Object> ioc=new HashMap<String,Object>();
private Map<String,Method> handlerMapping=new HashMap<String,Method>();
重写servlet的init()方法,在该方法中定义了大致的调用流程
public void init(ServletConfig config) throws ServletException {
//1.加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//2.解析配置文件,扫描相关的类
doScanner(contextConfig.getProperty("scanPackage"));
//3.初始化所有相关的类,并且将其放入到IOC容器中
doInstance();
//4.完成依赖注入
doAutoWired();
//5.初始化HandlerMapping映射
initHandlerMapping();
}
加载配置文件
通过输入流读取web.xml中所指定的Spring配置文件,将之保存到属性容器contextConfig中
private void doLoadConfig(String contextConfigLocation) {
InputStream is= this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
contextConfig.load(is);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (null!=is){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
解析配置文件,扫描相关的类
根据Spring配置文件所在位置进行扫描,只保留后缀名为.class结尾的文件,将文件名保存到classNames容器中,如果当前文件是文件夹,递归调用该方法,继续下一步扫描。
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 className = scanPackage + "." + file.getName().replace(".class", "");
classNames.add(className);
}
}
}
初始化所有相关的类,并且将其放入到IOC容器中
遍历classNames容器中所保存的类的全限命名,找到对应的类,判断类名前是否有hongController或者hongService注解,有则通过反射新建一个实例,模仿Spring的方式,默认类名首字母小写作为Map的key,实例对象作为对应的value,保存到ioc容器(HashMap)中。注意接口的实例化对象就是子类的实例化对象,因此需要保证接口只有一个子类,否则将不知道如何选择子类。
private void doInstance() {
if (classNames.isEmpty()){return;}
for (String className : classNames) {
Class<?> clazz= null;
try {
clazz = Class.forName(className);
if (clazz.isAnnotationPresent(hongController.class)){
String beanName=toLowerFirstCase(clazz.getSimpleName());
Object instance=clazz.newInstance();
ioc.put(beanName,instance);
}
else if (clazz.isAnnotationPresent(hongService.class)){
//1.默认的类名为首字母小写
String beanName=toLowerFirstCase(clazz.getSimpleName());
//2.自定义的beanName
hongService service=clazz.getAnnotation(hongService.class);
if (!"".equals(service.value()))
{
beanName=service.value();
}
Object instance=clazz.newInstance();
ioc.put(beanName,instance);
//3.创建接口的实例,怎么办?
for (Class<?> i : clazz.getInterfaces()) {
if (ioc.containsKey(i.getName()))
{
throw new Exception("The beanName has existed in ioc");
}
ioc.put(i.getSimpleName(),instance);
}
}
else {
continue;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
完成依赖注入
遍历ioc容器,完成依赖注入,本文实现的是通过字段注入,遍历中的每一个对象,通过类加载找到对象所对应的类,再遍历该类的所有字段,判断字段前是否有hongAutoWired注解,有则获取该字段的简单名,去匹配ioc容器的键值对,找寻该简单名所对应的实例对象,将该实例对象的值赋值给该字段。
private void doAutoWired() {
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(hongAutoWired.class)){continue;}
hongAutoWired autoWired=field.getAnnotation(hongAutoWired.class);
String beanName=autoWired.value().trim();
if ("".equals(beanName)) {
beanName=field.getType().getSimpleName();
}
//beanName这里没有问题吗?
field.setAccessible(true);//强制访问
try {
field.set(entry.getValue(),ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
初始化HandlerMapping映射
在这一步,同样是首先遍历ioc容器,找到容器对应实例所在的类,再继续寻找声明了hongController注解的类,用方法前定义的hongRequestMapping注解的值作为键,方法名作为值,保存到一个map容器中。注意:如果class前也声明了hongRequestMapping注解,则键需要class注解的值和方法注解值。
private void initHandlerMapping() {
if (ioc.isEmpty()){return;}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz=entry.getValue().getClass();
if (!clazz.isAnnotationPresent(hongController.class)){continue;}
String baseUrl="";
if (clazz.isAnnotationPresent(hongRequestMapping.class)){
hongRequestMapping requestMapping=clazz.getAnnotation(hongRequestMapping.class);
baseUrl=requestMapping.value();
}
for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(hongRequestMapping.class)){continue;}
hongRequestMapping requestMapping=method.getAnnotation(hongRequestMapping.class);
String url=("/"+baseUrl+"/"+requestMapping.value()).replaceAll("/+","/");//正则表达式替换任意个/为单个/
handlerMapping.put(url,method);
System.out.println("Mapped:" +url+":"+method);
}
}
}
以上就是ioc容器的简单实现,下面我们测试一下代码
代码测试
在serlvet的doDispatch(HttpServletRequest req, HttpServletResponse resp)中方法简单处理,根据浏览器传过来的请求,取得url并解析为相对路径,通过url在handlerMapping容器中找到对应的方法,通过反射调用该方法。
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String url=req.getRequestURI();
String contextUrl=req.getContextPath();
url=url.replaceAll(contextUrl,"").replaceAll("/+","/");
if (!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404 FileNotFound!");
return;
}
Method method=handlerMapping.get(url);
Map<String,String[]> params=req.getParameterMap();
String beanName=toLowerFirstCase(method.getDeclaringClass().getSimpleName());
method.invoke(ioc.get(beanName),new Object[]{req,resp,params.get("name")[0]});
}
我们运行该程序,在浏览器地址栏输入http://localhost:8080/spring/demo/add?name=“小红”
得到的页面为:
至此,我们完成ioc容器功能的简单实现。完整的代码可以在本人的github中获取,仓库地址为 https://github.com/Geek12580/Spring-IOC.
参考文章:
springIoC概述.
spring系列(一)——简介和IOC.