前言JavaWeb中的Servlet发展
Servlet开发 1.0Servlet开发 2.0Servlet开发 2.5Servlet开发 3.0 IOC实现
耦合/依赖MVC模式IOC - 控制反转DI - 依赖注入代码实现
配置文件Controller层Service层DAO层BeanFactoryBeanFactory的实现类 DOM操作
Step1 获取document对象Step2 将配置文件中的对象 存入容器Step3 组装对象间的依赖关系(依赖注入) 总结:
前言在了解IOC之前 我们需要先知道以下知识
JavaWeb中的Servlet发展
耦合/依赖
依赖注入DI/控制反转IOC
JavaWeb中的Servlet发展Servlet是JavaWeb即网络编程的基础,我们现在用到的大部分框架其底层都是Servlet。
Servlet开发 1.0如图可以看出 服务器中的Servlet很多,对于一个服务器来说,一个Servlet就会占用一个线程(资源),所以适当减少Servlet是未来目标
缺点:Servlet太多了,极大占用资源
Servlet开发 2.0实现方法:
前端发送请求时,会附带一个parameter,即http://localhost:8080:/pro?operate=add
FruitServlet 接收到请求后,通过使用 request.getParameter(“operate”)获取字符串 add,
通过if判断,进行之后的add方法。
@WebServlet("/fruit.do")
public class FruitServlet extends ViewbaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码
request.setCharacterEncoding("UTF-8");
String operate = request.getParameter("operate");//获取到Url的参数,operate
if(StringUtil.isEmpty(operate)){
operate = "index" ;
}
switch(operate){
case "index":
index(request,response);
break;
case "add":
add(request,response);
break;
case "del":
del(request,response);
break;
case "edit":
edit(request,response);
break;
case "update":
update(request,response);
break;
default:
throw new RuntimeException("operate值非法!");
}
}
private void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String fname = request.getParameter("fname");
Integer price = Integer.parseInt(request.getParameter("price")) ;
Integer fcount = Integer.parseInt(request.getParameter("fcount"));
String remark = request.getParameter("remark");
Fruit fruit = new Fruit(0,fname , price , fcount , remark ) ;
fruitDAO.addFruit(fruit);
response.sendRedirect("fruit.do");
}
}
优点:解决了一个服务器中的多个Servlet 占用资源
缺点:当服务太多时,switch-case代码太多,代码臃肿,不易维护
Servlet开发 2.5通过使用 反射 方法来 解决 switch-case的 代码臃肿 问题
我们将switch-case删除,改为反射方法,
通过URL知道当前需要的Servlet类+方法名;
使用反射 获取 Servlet类 并且运行 该方法;
package com.atguigu.servlet;
import java.lang.reflect.Method;
public class reflect {
public static void main(String[] args) throws Exception {
String a = "say";
Class clazz = Class.forName("com.atguigu.servlet.Person");// 获取运行时类
Object o = clazz.newInstance();// 将类实例化
Method[] declaredMethod = o.getClass().getDeclaredMethods();// 获取其内部方法
for (Method method : declaredMethod) {
System.out.println(method.getName());
}
}
}
class Person {
public void say() {
System.out.println("im a good boy");
}
public void bye() {
System.out.println("im a good boy");
}
}
优点:减少了 switch-case 的代码量
缺点:当我 拥有多个Servlet时,如FruitServlet及UserServlet 等等 上百个上千个时,我会有上百上千个 同样的 反射调用方法 ,会出现代码冗余
Servlet开发 3.0由于Servlet2.5中 每个控制器存在一个反射代码,代码冗余高。我们通过一个
Dispatcher / Servlet / 中央控制器 / 核心控制器 将 反射代码进行一个合并
实现方法:
准备一个配置文件:applicationContext.xml 其中记载着多个controller的信息(名字、目录)
将多个bean 标签内的 实体对象存放至一个容器中,即插即用
Step 1 读取配置文件applicationContext.xml
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
//1.创建documentBuilderFactory
documentBuilderFactory documentBuilderFactory = documentBuilderFactory.newInstance();
//2.创建documentBuilder对象
documentBuilder documentBuilder = documentBuilderFactory.newdocumentBuilder() ;
//3.创建document对象
document document = documentBuilder.parse(inputStream);
Step 2 获取所有 bean节点 并将其实例化 放入容器
private MapbeanMap = new HashMap<>(); //4.获取所有的bean节点 NodeList beanNodeList = document.getElementsByTagName("bean"); for(int i = 0 ; i 通 过URL传参过来的值,来获取Controller
@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置编码 request.setCharacterEncoding("UTF-8"); //假设url是: http://localhost:8080/pro15/hello.do?operate=add //那么servletPath是: /hello.do // 我的思路是: // 第1步: /hello.do -> hello 或者 /fruit.do -> fruit // 第2步: hello -> HelloController 或者 fruit -> FruitController String servletPath = request.getServletPath(); servletPath = servletPath.substring(1); int lastDotIndex = servletPath.lastIndexOf(".do") ; servletPath = servletPath.substring(0,lastDotIndex); Object controllerBeanObj = beanMap.get(servletPath);
通过operate参数 来调用 当前Controller 中的方法
遍历所有方法,通过方法名称 + 方法形参 + 方法形参类型 锁定需要执行的方法
用invoke(Object obj, Object... args) 执行 此方法
Method[] methods = controllerBeanObj.getClass().getDeclaredMethods(); for(Method method : methods){ if(operate.equals(method.getName())){ //1.统一获取请求参数 //1-1.获取当前方法的参数,返回参数数组 Parameter[] parameters = method.getParameters(); //1-2.parameterValues 用来承载参数的值 Object[] parameterValues = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; String parameterName = parameter.getName() ; //如果参数名是request,response,session 那么就不是通过请求中获取参数的方式了 if("request".equals(parameterName)){ parameterValues[i] = request ; }else if("response".equals(parameterName)){ parameterValues[i] = response ; }else if("session".equals(parameterName)){ parameterValues[i] = request.getSession() ; }else{ //从请求中获取参数值 String parameterValue = request.getParameter(parameterName); String typeName = parameter.getType().getName(); Object parameterObj = parameterValue ; if(parameterObj!=null) { if ("java.lang.Integer".equals(typeName)) { parameterObj = Integer.parseInt(parameterValue); } } parameterValues[i] = parameterObj ; } } //2.controller组件中的方法调用 method.setAccessible(true); Object returnObj = method.invoke(controllerBeanObj,parameterValues); //3.视图处理 String methodReturnStr = (String)returnObj ; if(methodReturnStr.startsWith("redirect:")){ //比如: redirect:fruit.do String redirectStr = methodReturnStr.substring("redirect:".length()); response.sendRedirect(redirectStr); }else{ super.processTemplate(methodReturnStr,request,response); // 比如: "edit" } } }IOC实现 耦合/依赖依赖 指的时,A类的成功运行 需要使用 B类中某个方法。这样A类 就对 B类存在依赖关系。即 B类若不在 A类无法执行。
这种依赖 也就等同于 耦合
我们的追求就是 高内聚,低耦合
如图所示:
Controller Service Dao 三者相互依赖。此为
MVC模式MVC : Model(模型)、View(视图)、Controller(控制器)
视图层:用于做数据展示以及和用户交互的一个界面
控制层:能够接受客户端的请求,具体的业务功能还是需要借助于模型组件来完成
模型层:模型分为很多种:有比较简单的pojo/vo(value object),有业务模型组件,有数据访问层组件pojo/vo : 值对象 AO : 数据访问对象 BO : 业务对象IOC - 控制反转之前在Servlet中,我们创建service对象 , FruitService fruitService = new FruitServiceImpl();
DI - 依赖注入
这句话如果出现在servlet中的某个方法内部,那么这个fruitService的作用域(生命周期)应该就是这个方法级别;
如果这句话出现在servlet的类中,也就是说fruitService是一个成员变量,那么这个fruitService的作用域(生命周期)应该就是这个servlet实例级别.之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中
因此,我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转.之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();
代码实现 配置文件
那么,控制层和service层存在耦合。之后,我们将代码修改成FruitService fruitService = null ;然后,在配置文件中配置:
先写配置文件,在配置文件中,写好所要用到的类,并声明其 名字 + 类路径。
// application.xmlController层如下所示,在FruitController中 需要使用 FruitService
public class FruitController { private FruitService fruitService = null ; private String update(Integer fid , String fname , Integer price , Integer fcount , String remark ){ //3.执行更新 fruitService.updateFruit(new Fruit(fid,fname, price ,fcount ,remark )); return "redirect:fruit.do"; } private String edit(Integer fid , HttpServletRequest request){ if(fid!=null){ Fruit fruit = fruitService.getFruitByFid(fid); request.setAttribute("fruit",fruit); return "edit"; } return "error" ; } }Service层写一个 Service 接口,方便 未来维护
public interface FruitService { //根据id查看指定库存记录 Fruit getFruitByFid(Integer fid); //修改特定库存记录 void updateFruit(Fruit fruit); }FruitServiceImpl 一个 Service 的 实现类
public class FruitServiceImpl implements FruitService { private FruitDAO fruitDAO = null ; @Override public Fruit getFruitByFid(Integer fid) { return fruitDAO.getFruitByFid(fid); } @Override public void updateFruit(Fruit fruit) { fruitDAO.updateFruit(fruit); } }DAO层FruitDAOImpl 继承 JDBC 的 baseDAO 同时实现 FruitDAO接口
public class FruitDAOImpl extends baseDAOBeanFactoryimplements FruitDAO { @Override public Fruit getFruitByFid(Integer fid) { return super.load("select * from t_fruit where fid = ? " , fid); } @Override public void updateFruit(Fruit fruit) { String sql = "update t_fruit set fname = ? , price = ? , fcount = ? , remark = ? where fid = ? " ; } } BeanFactory 是用来 解析XML文件,并 获取 任何 XML文件中 声明的对象的。
public interface BeanFactory { Object getBean(String id); }BeanFactory的实现类 DOM操作 Step1 获取document对象读取配置文件
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml"); //1.创建documentBuilderFactory documentBuilderFactory documentBuilderFactory = documentBuilderFactory.newInstance(); //2.创建documentBuilder对象 documentBuilder documentBuilder = documentBuilderFactory.newdocumentBuilder() ; //3.创建document对象 document document = documentBuilder.parse(inputStream);Step2 将配置文件中的对象 存入容器解析document
通过 bean标签 id属性 class属性 + 反射 = 实例化对象 + 存入容器m
//4.获取所有的bean节点 NodeList beanNodeList = document.getElementsByTagName("bean"); for(int i = 0 ; iStep3 组装对象间的依赖关系(依赖注入) 上一步我们 通过 bean标签 id属性 class属性 + 反射 = 实例化对象
但是 如下所示 XML文件中 property标签 还没有解析 ,说明 我们还没进行依赖注入
// application.xml依赖注入
通过DOM操作 来解析document文件
用 if判断 bean标签 中是否含有 property标签,
有 则通过 反射 将属性 注入
for(int i = 0 ; i总结:
通过 一个一个的写Servlet类 实现一个基本功能
Servlet类太多了 将一类实体的 Servlet进行合并 同时使用 switch-case 来实现功能
**通过反射 **来获取方法 而不是 使用 switch-case,进而缩短了代码量
由于多个Servlet 每个都要写反射代码(水果类、员工类、顾客类)导致代码冗余,通过提取公共部分 来精简代码
这个公共部分(dispatcher) 需要 通过URL参数 来判断需要调用哪个类&哪个方法,当判断成功后,直接调用方法。即 Controller 与 客户端中间 隔了一个 dispatcher ,dispatcher 负责 调用controller中的方法。
引入Service 进一步细化 整个流程,如上所示,我们有很多操作相关的代码 依然存放在controller中,将这些方法 提取出来 单独放在一个层中 即 Service层。
至此 可以发现 我们现在有很多层 Controller、Service、DAO。这些层 存在一个问题就是:依赖太多,耦合太高 。
解决方法:通过配置文件 建立一个 工厂,BeanFactory,这个工厂为我们 给每个对象实例化。
核心思想: 改变 被依赖类的生命周期。
原:FruitService fruitService = new FruitServiceImpl(); 写在某个类中
现:FruitService fruitService = null; ,通过反射 将此对象赋值,
原:fruitService 的生命周期 随着 某个类or某个方法(主要看在哪里 new出来的),当某个类销毁 则 fruitService也同样销毁
现:fruitService 被放置在 一个 Map容器中,其生命周期不会随着 依赖他的对象 而改变,仅由 Map容器控制。
好处: 利于代码维护,
未来海量的依赖对象如果都通过FruitService fruitService = new FruitServiceImpl();
那么如果我改变了FruitServiceImpl()->FruitServiceImpl2(),我需要 更改所有 FruitService fruitService = new FruitServiceImpl();
使用配置文件的方式,我只需要更改 配置文件中的 bean标签 即可,
大大缩减代码维护的成本。



