Java Web实现IOC控制反转之依赖注入
控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。
控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。
依赖注入应用比较广泛。本文介绍java实现一个简单的依赖注入
简单而言,当你在某一个类中需要调用其他的类并且生成对象,大部分情况是new一个对象,此时如果你不确定要new哪一个对象,你就需要为所有的类作if或者switch判断,在不同情况下new不同的对象,然后给他们属性赋值
使用最多的地方就是JavaBean类和他们的对象了,假设项目的Model里面有Teacher,StudentClass,分别代表老师和班级两个javaBean类
public class Teacher { private String name; private String id; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String toString() { return "Teacher [name=" + name + ", id=" + id + "]"; } }
public class SchoolClass { private String schoolClassName; private String schoolClassId; private Teacher manager; public String getSchoolClassName() { return schoolClassName; } public void setSchoolClassName(String schoolClassName) { this.schoolClassName = schoolClassName; } public String getSchoolClassId() { return schoolClassId; } public void setSchoolClassId(String schoolClassId) { this.schoolClassId = schoolClassId; } public Teacher getManager() { return manager; } public void setManager(Teacher manager) { this.manager = manager; } @Override public String toString() { return "The manager of "+schoolClassName+"("+schoolClassId+")"+"is Teacher"+manager.toString(); }
此时,我们有一个老师,两个班级,那么在实际使用中是不是就要new一个老师,然后使用getter,setter为他赋值属性,然后new两个班级,并且按照老师的做法赋值,这样如果有很多的老师和班级,那么就要不停的在java代码中new,new,new......
而且每次多一个老师,班级,我们就要修改java代码,重新编译,这样耦合度就很高,能不能使用一个工具类自动新建对象,而对象的信息保存在一个文本中
IoC的设计模式解决了这个问题,使用依赖注入,我们把类以及类相依赖的类放到动态修改的文本当中,每次从文本中读取,然后根据文本信息,动态的new出这些对象,做到灵活,多样化,易扩展。这样不在需要去修改或者添加java代码,代码重复性也减少。
看看设计图:
开始,新建一个web dynamic项目
目录结构如下:
其中两个javaBean放在app包中,确切说这是pojo类
ioc包里面是ioc核心控制器
test包是一个servlet,主要用于Ioc是否成功的测试
由于个人觉得xml文件的书写和读取,传输都不是很好,尤其在spring,struts的配置均采用xml,实在厌恶
之前在做php开发的时候,配置文件一般是json的格式或者php对象的格式(好吧,两者只是本质有区别,事实上形式相似)
所以,我这次异想天开的在自己的IoC中使用Json作为依赖注入的动态配置文件取代大部分框架使用的xml文件
如下:
[ { "bean": "cn.cslg.app.Teacher", "id": "class_manager", "properties": { "name": "黄有为", "id": "200500027" } }, { "bean": "cn.cslg.app.SchoolClass", "id": "class1", "properties": { "schoolClassId": "Z094141", "schoolClassName": "软件工程" }, "ref": { "manager": "class_manager" } }, { "bean": "cn.cslg.app.SchoolClass", "id": "class2", "properties": { "schoolClassId": "Z094142", "schoolClassName": "软件工程" }, "ref": { "manager": "class_manager" } } ]
IocListener代码如下:
package cn.cslg.ioc; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; @WebListener public class IocListener implements ServletContextListener{ private final String filename = "/WEB-INF/ioc.json"; @Override public void contextInitialized(ServletContextEvent sce) { // TODO Auto-generated method stub ServletContext context =sce.getServletContext(); String configFile = context.getInitParameter("ioc-config"); String path = context.getRealPath(filename); try { if (configFile != null) { path = context.getRealPath(configFile); } ConfigParser parser = new ConfigParser(path); context.setAttribute("APPLICATION_CONTEXT_BEANS", parser.parse()); } catch (IocException e) { // TODO: handle exception e.printStackTrace(); } } @Override public void contextDestroyed(ServletContextEvent sce) { // TODO Auto-generated method stub } }
实现对servlet上下文的监听,主要是调用了ConfigParser创建对象并在servlet中增加了一个值
APPLICATION_CONTEXT_BEANS
将刚刚创建的beans对象保存在其中,以便servlet使用过程中使用
ConfigParser实现了控制反转的依赖注入,类构成如下:
类私有属性:
private HashMap<String, Object> beansMap = new HashMap<>(); private String jsonString; private String filename;
类构造方法:
public ConfigParser(String filename) { super(); this.filename = filename; this.jsonString = ReadJsonFile(filename); }
使用ReadJsonFile对json进行解析,返回json的字符串,交给jsonString
ReadJsonFile代码如下:
private String ReadJsonFile(String path) { File file = new File(path); BufferedReader reader = null; String laststr = ""; try { reader = new BufferedReader(new FileReader(file)); String tempString = null; int line = 1; while ((tempString = reader.readLine()) != null) { System.out.println("line " + line + ": " + tempString); laststr = laststr + tempString; line++; } reader.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e1) { } } } return laststr; }
其中解析json(JsonArray,JsonObject)需要用到json-lib,javaBean是解析需要用到common-beanutils
请使用gradle或者手动导入这两个包,如使用gradle如下:
repositories { mavenCentral() jcenter() maven { url "http://repo.spring.io/release" } } dependencies { compile group: 'net.sf.json-lib', name: 'json-lib', version: '2.4' compile group: 'commons-beanutils', name: 'commons-beanutils', version: '1.9.3' compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0' testImplementation 'junit:junit:4.12' }
instance方法,第一步“创建对象”,class.forname和class.newInstance是核心,有了他们实现了类->对象的创建
public void instantiate() throws IocException { JSONArray beans = (JSONArray) JSONArray.fromObject(jsonString); try { for (Iterator it = beans.iterator(); it.hasNext();) { JSONObject bean = (JSONObject) it.next(); String className = (String) bean.get("bean"); String idName = (String) bean.get("id"); Object obj; Class<?> class1 = Class.forName(className); obj = class1.newInstance(); beansMap.put(idName, obj); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
injection方法,为instance创建的对象赋值属性,主要包括了普通值,引用值
public void injection() throws IocException { JSONArray beans = (JSONArray) JSONArray.fromObject(jsonString); try { for (Iterator it = beans.iterator(); it.hasNext();) { JSONObject bean = (JSONObject) it.next(); String beanId = bean.get("id").toString(); Object beanObj = beansMap.get(beanId); if (beanObj != null) { PropertyDescriptor pds[] = PropertyUtils.getPropertyDescriptors(beanObj); JSONObject properties = (JSONObject) bean.get("properties"); JSONObject ref = (JSONObject) bean.get("ref"); Iterator keyIter; String key; String value; if (properties != null) { keyIter = properties.keys(); while (keyIter.hasNext()) { key = (String) keyIter.next(); for (PropertyDescriptor pd : pds) { if (pd.getName().equals(key)) { value = properties.get(key).toString(); if (value != null) pd.getWriteMethod().invoke(beanObj, (Object) value); } } } } if (ref != null) { keyIter = ref.keys(); while (keyIter.hasNext()) { key = keyIter.next().toString(); for (PropertyDescriptor pd : pds) { if (pd.getName().equals(key)) { value = ref.get(key).toString(); if (value != null) pd.getWriteMethod().invoke(beanObj, beansMap.get(value)); } } } } } } } catch (Exception e) { e.printStackTrace(); } }
注意在json中,配置了对象的赋值,properties是普通的属性值,可以int,String值。而ref则是引用值,引用了其他的对象,这里是Teacher对象,表示班级由某一个老师管理。注意处理时要区别对待
其中使用了两重循环迭代,第一重遍历bean对象是数组JsonArray,第二重遍历bean下的properties或者是ref是对象JsonObject,从json文件中不难看出其迭代结构
核心方法是invoke,替代了getter,setter,直接对对象的属性进行赋值(初始化),值从json文件的properties和ref中获取,并且一一对应的赋值
注意java中,json对象是不能直接转化为JavaBean对象的,需要对其一层层遍历手动赋值,php则可以直接将json转化为对象,因为php本身是动态语言!
最后,parse方法返回一个HashMap,存储了所有生成的JavaBean对象:
public HashMap<String, Object> parse() throws IocException { instantiate(); injection(); return beansMap; }
使用,servlet进行测试:
TestIocServlet.java的代码:
@WebServlet("/TestIocServlet") public class TestIocServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setCharacterEncoding("utf-8"); resp.setContentType("text/html"); try (PrintWriter out = resp.getWriter()) { @SuppressWarnings("unchecked") HashMap<String, Object> beans = (HashMap<String, Object>) this.getServletContext() .getAttribute("APPLICATION_CONTEXT_BEANS"); Set<String> keys = beans.keySet(); for (String key : keys) { out.println(key + ": " + beans.get(key) + "<br/>"); } } } }
启动tomcat运行servlet,浏览器效果如下:
很明显,已经实现了对文本json文件的动态对象生成和赋值,不需要在java文件中重复new对象并且赋值
对了,项目中有个异常处理文件,IocException.java如下:
public class IocException extends Exception{ public IocException(Throwable cause){ super(cause); } }