文章目录
前言
在了解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
的信息(名字、目录)
<?xml version="1.0" encoding="utf-8"?>
<beans>
<!-- 这个bean标签的作用是 将来servletpath中涉及的名字对应的是fruit,那么就要FruitController这个类来处理 -->
<bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/>
<bean id="user" class="com.atguigu.fruit.controllers.UserController"/>
<bean id="order" class="com.atguigu.fruit.controllers.OrderController"/>
<bean id="product" class="com.atguigu.fruit.controllers.ProductController"/>
</beans>
-
将多个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 Map<String,Object> beanMap = new HashMap<>(); //4.获取所有的bean节点 NodeList beanNodeList = document.getElementsByTagName("bean"); for(int i = 0 ; i<beanNodeList.getLength() ; i++){ Node beanNode = beanNodeList.item(i); if(beanNode.getNodeType() == Node.ELEMENT_NODE){ Element beanElement = (Element)beanNode ; String beanId = beanElement.getAttribute("id"); String className = beanElement.getAttribute("class"); Class controllerBeanClass = Class.forName(className); Object beanObj = controllerBeanClass.newInstance() ; beanMap.put(beanId , beanObj) ;// 对XML文件进行解析,获取配置文件中的所有对象 } }
-
通 过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();
这句话如果出现在servlet中的某个方法内部,那么这个fruitService的作用域(生命周期)应该就是这个方法级别;
如果这句话出现在servlet的类中,也就是说fruitService是一个成员变量,那么这个fruitService的作用域(生命周期)应该就是这个servlet实例级别. - 之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中
因此,我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转.
DI - 依赖注入
- 之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();
那么,控制层和service层存在耦合。 - 之后,我们将代码修改成FruitService fruitService = null ;然后,在配置文件中配置:
代码实现
配置文件
先写配置文件,在配置文件中,写好所要用到的类,并声明其 名字 + 类路径。
// application.xml
<?xml version="1.0" encoding="utf-8"?>
<beans>
<bean id="fruitDAO" class="com.alex.fruit.dao.impl.FruitDAOImpl"/>
<bean id="fruitService" class="com.alex.fruit.service.impl.FruitServiceImpl">
<!-- property标签用来表示属性;name表示属性名;ref表示引用其他bean的id值-->
<property name="fruitDAO" ref="fruitDAO"/>
</bean>
<bean id="fruit" class="com.alex.fruit.controllers.FruitController">
<property name="fruitService" ref="fruitService"/>
</bean>
</beans>
Controller层
如下所示,在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 BaseDAO<Fruit> implements 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
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 ; i<beanNodeList.getLength() ; i++){
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
Element beanElement = (Element)beanNode ;
String beanId = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
Class beanClass = Class.forName(className);
//创建bean实例
Object beanObj = beanClass.newInstance() ;
//将bean实例对象保存到map容器中
beanMap.put(beanId , beanObj) ;
//到目前为止,此处需要注意的是,bean和bean之间的依赖关系还没有设置
}
}
Step3 组装对象间的依赖关系(依赖注入)
上一步我们 通过 bean标签 id属性 class属性 + 反射 = 实例化对象
但是 如下所示 XML文件中 property标签 还没有解析 ,说明 我们还没进行依赖注入
// application.xml
<?xml version="1.0" encoding="utf-8"?>
<beans>
<bean id="fruitDAO" class="com.alex.fruit.dao.impl.FruitDAOImpl"/>
<bean id="fruitService" class="com.alex.fruit.service.impl.FruitServiceImpl">
<!-- property标签用来表示属性;name表示属性名;ref表示引用其他bean的id值-->
<property name="fruitDAO" ref="fruitDAO"/>
</bean>
<bean id="fruit" class="com.alex.fruit.controllers.FruitController">
<property name="fruitService" ref="fruitService"/>
</bean>
</beans>
依赖注入
通过DOM操作 来解析Document文件
用 if判断 bean标签 中是否含有 property标签,
有 则通过 反射 将属性 注入
for(int i = 0 ; i<beanNodeList.getLength() ; i++){
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE) {
Element beanElement = (Element) beanNode;
String beanId = beanElement.getAttribute("id");
NodeList beanChildNodeList = beanElement.getChildNodes();
for (int j = 0; j < beanChildNodeList.getLength() ; j++) {
Node beanChildNode = beanChildNodeList.item(j);
if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
Element propertyElement = (Element) beanChildNode;
String propertyName = propertyElement.getAttribute("name");
String propertyRef = propertyElement.getAttribute("ref");
//1) 找到propertyRef对应的实例
Object refObj = beanMap.get(propertyRef);
//2) 将refObj设置到当前bean对应的实例的property属性上去
Object beanObj = beanMap.get(beanId);
Class beanClazz = beanObj.getClass();
Field propertyField = beanClazz.getDeclaredField(propertyName);
propertyField.setAccessible(true);
propertyField.set(beanObj,refObj);// 通过反射 注入
}
}
}
}
总结:
-
通过 一个一个的写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标签 即可,
大大缩减代码维护的成本。