IOC控制反转

前言

在了解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");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xX0a5AAL-1644228667617)(C:\Users\12497\AppData\Roaming\Typora\typora-user-images\image-20220207134108317.png)]

优点:减少了 switch-case 的代码量

缺点:当我 拥有多个Servlet时,如FruitServletUserServlet 等等 上百个上千个时,我会有上百上千个 同样的 反射调用方法 ,会出现代码冗余

Servlet开发 3.0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LfbPaZHD-1644228667618)(C:\Users\12497\AppData\Roaming\Typora\typora-user-images\image-20220207122009484.png)]

由于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 - 控制反转

  1. 之前在Servlet中,我们创建service对象 , FruitService fruitService = new FruitServiceImpl();
    这句话如果出现在servlet中的某个方法内部,那么这个fruitService的作用域(生命周期)应该就是这个方法级别;
    如果这句话出现在servlet的类中,也就是说fruitService是一个成员变量,那么这个fruitService的作用域(生命周期)应该就是这个servlet实例级别.
  2. 之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中
    因此,我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转.

DI - 依赖注入

  1. 之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();
    那么,控制层和service层存在耦合。
  2. 之后,我们将代码修改成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);// 通过反射 注入
            }
        }
    }
}

总结:

  1. 通过 一个一个的写Servlet类 实现一个基本功能

  2. Servlet类太多了 将一类实体的 Servlet进行合并 同时使用 switch-case 来实现功能

  3. **通过反射 **来获取方法 而不是 使用 switch-case,进而缩短了代码量

  4. 由于多个Servlet 每个都要写反射代码(水果类、员工类、顾客类)导致代码冗余,通过提取公共部分 来精简代码

    这个公共部分dispatcher) 需要 通过URL参数 来判断需要调用哪个类&哪个方法,当判断成功后,直接调用方法。即 Controller 与 客户端中间 隔了一个 dispatcher ,dispatcher 负责 调用controller中的方法。

  5. 引入Service 进一步细化 整个流程,如上所示,我们有很多操作相关的代码 依然存放在controller中,将这些方法 提取出来 单独放在一个层中 即 Service层。

  6. 至此 可以发现 我们现在有很多层 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标签 即可,

    大大缩减代码维护的成本

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值