JAVAWEB(四)IOC,业务层,servlet的生命周期


继续后端之旅

一. MVC

什么是MVC

MVC是组织代码的一种方式,一种约定俗成管理web项目的有效方式

Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,主要提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。主要使用的技术:数据模型:实体类(JavaBean),数据访问:JDBC,Hibernate等。

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,比如JSP,Html等

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。主要使用的技术:servlet,Struts中的Action类等

MVC是一个框架模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。最典型的MVC就是JSP + servlet + javabean的模式

Servlet的初始化方法

前面做servlet优化的时候,我们重写了继承自HttpServlet的init方法。HttpServlet有带参和不带参的init方法,我们重写的是不带参的init方法

这是HttpServlet的父类GenericServlet中的两个init方法

publicvoid init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}

public void init() throws ServletException {
}

从继承树来看,Servlet接口声明了init方法,GenericServlet也只是个抽象类,给空参init补了空的方法体,重载了一个带参的init方法。HttpServlet没有重写init。因此,我们自定义的servlet结构可以重写init方法,执行一些个性化的初始化操作

感觉这个init方法就是专门留给我们的,它本身没有方法体,重写init方法也不会影响servlet底层

在ServletConfig实现类存储初始化配置参数

这里的用法其实很绕,我没相同为什么要这么做

(1)在WEB-INF/web.xml目录下,有这么一个标签<init-param></init-param>,在这对标签里边可以定义参数的name和value,参数以键值对的形式存在

<servlet>
    <servlet-name>Demo01Servlet</servlet-name>
    <servlet-class>com.atguigu.servlet.Demo01Servlet</servlet-class>
    <init-param>
        <param-name>hello</param-name>
        <param-value>world</param-value>
    </init-param>
    <init-param>
        <param-name>uname</param-name>
        <param-value>jim</param-value>
    </init-param>
</servlet>

(2)获取web.xml中init-param标签里的参数比较麻烦,在我们重写的init方法中,通过ServletConfig接口的实现类的实例来获取初始化配置参数,换句或说ServletConfig实例是用来管理初始化配置参数的

ServletConfig实例调用了getInitParameter方法来获取到init-param参数的值

public class Demo01Servlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        ServletConfig config = getServletConfig();
        String initValue = config.getInitParameter("hello");
        System.out.println("initValue = " + initValue);
    }
}

如果我们想要在Servlet初始化时做一些准备工作,那么我们可以重写init方法
我们可以通过如下步骤去获取初始化设置的数据

  • 获取config对象:ServletConfig config = getServletConfig();
  • 获取初始化参数值: config.getInitParameter(key);

注解配置初始化参数

和之前一样,也可以用注解代替在web.xml文件中配置初始化参数

@WebServlet(urlPatterns = {"/demo01"} ,
        initParams = {
            @WebInitParam(name="hello",value="world"),
            @WebInitParam(name="uname",value="jim")
        }
)

在ServletContext实现类存储参数

之前就说过,tomcat容器中又有3个map容器。HttpServletRequest,HttpSession,ServletContext

ServletContext是一个接口,他和ServletConfig又有关系

GenericServlet中定义了这么一个方法,居然是ServletConfig实例去调用了getServletContext方法

//来自GenericServlet抽象类
public ServletContext getServletContext() {
    return this.getServletConfig().getServletContext();
}

而ServletConfig接口的声明中声明了getServletContext方法

public interface ServletConfig {
    String getServletName();

    ServletContext getServletContext();

    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();
}

存储参数的方法和ServletConfig差不多,都是现在web.xml中声明键值对,这次用的是另外一组标签<context-param></context-param>

但是有区别额是,init-param标签是写在servlet标签里面,而context-param标签写在外面

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

(1)在重写的空参init方法中获取ServletContext实例中的参数

public void init() throws ServletException {
    ServletConfig config = getServletConfig();
    String initValue = config.getInitParameter("hello");
    System.out.println("initValue = " + initValue);

    ServletContext servletContext = getServletContext();
    String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
    System.out.println("contextConfigLocation = " + contextConfigLocation);
}

(2)在重写的service方法中获取ServletContext实例中的参数,这就麻烦一点,必须通过HttpServletRequest实例来获取

@Override
public void service(HttpServletRequest request, HttpServletResponse response){
    request.getServletContext();
    request.getSession().getServletContext();
}

业务层-引入service

组织管理无论是对人还是对代码都是随着量变而越来越重要

当代码量越来越大,组织管理的需求也就越来越大。即便如此,软件还是会随着时间变得越来越臃肿,越来越难以维护,这似乎是软件不可避免的生命周期

MVC是组织代码的一种方式,一种约定俗成管理web项目的有效方式

Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,主要提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。主要使用的技术:数据模型:实体类(JavaBean),数据访问:JDBC,Hibernate等。

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,比如JSP,Html等。数据展示以及和用户进行交互的窗口

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。主要使用的技术:servlet,Struts中的Action类等

MVC是一个框架模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。最典型的MVC就是JSP + servlet + javabean的模式


什么是业务层

  1. Model1和Model2
    MVC : Model(模型)、View(视图)、Controller(控制器)
    视图层:用于做数据展示以及和用户交互的一个界面
    控制层:能够接受客户端的请求,具体的业务功能还是需要借助于模型组件来完成
    模型层:模型分为很多种:有比较简单的pojo/vo(value object),有业务模型组件,有数据访问层组件

    1. pojo/vo : 值对象
    2. DAO : 数据访问对象
    3. BO : 业务对象
  2. 区分业务对象和数据访问对象:
    1) DAO中的方法都是单精度方法或者称之为细粒度方法。什么叫单精度?原子操作。一个方法只考虑一个操作,比如添加,那就是insert操作、查询那就是select操作…。BaseDAO里边就是通用的增删改查方法,由他们组合形成DAOImpl中的单精度方法
    2) BO中的方法属于业务方法,也实际的业务是比较复杂的,因此业务方法的粒度是比较粗的
    注册这个功能属于业务功能,也就是说注册这个方法属于业务方法。
    那么这个业务方法中包含了多个DAO方法。也就是说注册这个业务功能需要通过多个DAO方法的组合调用,从而完成注册功能的开发。
    注册:
    1. 检查用户名是否已经被注册 - DAO中的select操作
    2. 向用户表新增一条新用户记录 - DAO中的insert操作
    3. 向用户积分表新增一条记录(新用户默认初始化积分100分) - DAO中的insert操作
    4. 向系统消息表新增一条记录(某某某新用户注册了,需要根据通讯录信息向他的联系人推送消息) - DAO中的insert操作
    5. 向系统日志表新增一条记录(某用户在某IP在某年某月某日某时某分某秒某毫秒注册) - DAO中的insert操作
    6. …

  3. 在库存系统中添加业务层组件

引入业务层之后的javaweb架构

在这里插入图片描述
在引入业务层之前,控制器只负责业务相关的特有操作,而那些通用的处理,比如获取参数,跳转等,都有中央控制器负责

引入业务层之后,由业务层来负责实现业务相关的特有操作,控制器也终于有了控制对象,控制器负责调用业务层提供的业务方法。在上图中我们也可以看到,控制器不再调用DAO组件,而是由业务层XxxService来调用DAO组件实现业务方法

业务层的结构

和DAO的结构差不多,每一张数据表都对应一个业务接口XxxService,一个接口实现类XxxServiceImpl。比如说FruitService接口+FruitServiceImpl实现类
在这里插入图片描述

IOC

IOC技术

简介

IOC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合,更优良的程序

传统应用程序都是由我们在类内部主动创建依赖对象(在一个类的内部实例化其他类),从而导致类与类之间高耦合,难于测试;

有了IOC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的使程序的整个体系结构变得非常灵活

在运行期,在外部容器动态的将依赖对象注入组件,当外部容器启动后,外部容器就会初始化。创建并管理bean对象,以及销毁他,这种应用本身不负责依赖对象的创建和维护,依赖对象的创建和维护是由外部容器负责的称为控制反转。

IOC(控制反转)和 DI(依赖注入)

IOC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IOC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。

DI(依赖注入)。IOC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的

为什么不再重写doPost方法

回顾代码的时候突然发现,从引入中央控制器开始,就不再需要重写doPost之类的doXxx方法了

其原因在于,我们在中央控制器中重写了service方法,改变了service内部的逻辑

在servlet继承树中,HttpServlet类实现了service方法,在其内部实现对doXxx方法的调用

protectedvoid service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader("If-Modified-Since");
            } catch (IllegalArgumentException var9) {
                ifModifiedSince = -1L;
            }

            if (ifModifiedSince < lastModified / 1000L * 1000L) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

但是中央控制器重写service方法的逻辑之后,就不再去调用那些doXxx方法了,而是去调用控制器中的方法。于是我们也就不再需要重写doXxx方法

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //设置编码
    request.setCharacterEncoding("UTF-8");
    //假设url是:  http://localhost:8080/pro15/hello.do
    //那么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 = beanFactory.getBean(servletPath);

    String operate = request.getParameter("operate");
    if(StringUtil.isEmpty(operate)){
        operate = "index" ;
    }

    try {
        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"
                }
            }
        }

        /*
        }else{
            throw new RuntimeException("operate值非法!");
        }
        */
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

依赖对象-类之间的关系

这涉及到另一个话题,类之间的关系

类之间的关系包括继承、实现、依赖、关联、聚合、组合

依赖:类A使用到了另一个类B,而这种使用关系是具有偶然性的、、临时性的、非常弱的。在代码层面,为类B作为参数被类A在某个method方法中使用

关联:两个类、或者类与接口之间语义级别的一种强依赖关系。在代码层面,类B的实例作为类A的属性存在

聚合:关联关系的一种特例。

组合:也是关联关系的一种特例,比聚合更强。但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束

有很多事物间的关系要想准备定位是很难的,前面也提到,这几种关系都是语义级别的,所以从代码层面并不能完全区分各种关系

但总的来说,后几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖

层之间的耦合

耦合,也可以称之为依赖。耦合描述的是关系,关系存在于多个对象之间
在这里插入图片描述
比如说在上图中,中央控制器,控制器,业务层,DAO之间是耦合的,向下依赖的,上层依赖下层。删掉控制器,业务层和DAO都不会报错,但是中央控制器会报错

高内聚,低耦合(0耦合)。当达成0耦合,意味着完全模块化,定制化

解耦合

我们有这样一个目标:删掉下层,上层不报错。比如删掉业务层,控制器层不报错

前面已经说了,业务层负责业务的具体实现,控制器层只需要调用业务层的方法。控制器层是利用业务层实例来调用业务层的方法,这就带来了耦合的问题

private FruitService fruitService = new FruitService();

为了解耦合,首先就要去除这个实例

private FruitService fruitService = null ;

但是,去除了业务层实例怎么调用业务层的方法呢???

还记得学习JDBC的时候,为了隐藏第三方API,我们使用配置文件,记录下接口实现类的地址字符串

配置文件

在这里似乎采用了同样的办法,在applicationContext.xml(也就是之前用于创建beanMap容器的xml配置文件)中配置控制器
从标签来看也印证了前面的说法,业务层的结构和DAO层类似,接口+实现类的结构

<beans>
	//用于实现ioc
    <bean id="fruitDAO" class="com.atguigu.fruit.dao.impl.FruitDAOImpl"/>
    <bean id="fruitService" class="com.atguigu.fruit.service.impl.FruitServiceImpl"/>
    
    //用于创建beanMap容器
    <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/>
</beans>
解析配置文件

建立一个ioc目录,目录下创建一个接口BeanFactory,用于解析配置文件

在BeanFactory接口中定义一个方法,通过配置文件中的id,返回一个对应类的实例

Object getBean(String id);

然后创建一个类ClassPathXMLApplicationContext,用于实现BeanFactory接口

public class ClassPathXmlApplicationContext implements BeanFactory {

    private Map<String,Object> beanMap = new HashMap<>();
    @Override
    public Object getBean(String id) {
        return beanMap.get(id);
    }
}

这里也出现了一个容器beanMap,他和之前在中央控制器中创建的容器是一样的

中央控制器DispatcherServlet重写了两个方法:
(1)重写init方法,解析applicationContext.xml配置文件,创建包含键值对id+对应类实例的beanMap容器
(2)重写service方法,解析url确定调用控制器的哪一个方法,统一获取参数,视图处理

于是我们将中央控制器中重写的init方法当中创建beanMap容器的代码提取出来,放到ClassPathXMLApplicationContext这个实现类的空参构造器中

public class ClassPathXmlApplicationContext implements BeanFactory {

    private Map<String,Object> beanMap = new HashMap<>();

    public ClassPathXmlApplicationContext(){
        try {
            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);

            //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之间的依赖关系还没有设置
                }
            }
            //5.组装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");
                    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);
                        }
                    }
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }


    @Override
    public Object getBean(String id) {
        return beanMap.get(id);
    }
}

进一步解耦合
<beans>
	//用于实现ioc
    <bean id="fruitDAO" class="com.atguigu.fruit.dao.impl.FruitDAOImpl"/>
    <bean id="fruitService" class="com.atguigu.fruit.service.impl.FruitServiceImpl"/>
    
    //用于创建beanMap容器
    <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/>
</beans>

回顾前面完成的applicationContext.xml配置,他能提供所有需要的实例吗??可以看到控制器层实例,业务层实例,DAO实例都有了。但是这意味着,每一层想要使用实例,就必须在自己这一层实例化IOC层,但是每一层往往只需要IOC容器中的一个实例,其他用不着啊,因为上层只依赖下层。比如说,控制器层只需要业务层的实例,也就是上边的com.atguigu.fruit.service.impl.FruitServiceImpl。

有没有办法,在一个足够高的层次,只实例化一次IOC层,然后其他各层都可以从中取出需要的实例

<beans>

    <bean id="fruitDAO" class="com.atguigu.fruit.dao.impl.FruitDAOImpl"/>
    <bean id="fruitService" class="com.atguigu.fruit.service.impl.FruitServiceImpl">
        <!-- property标签用来表示属性;name表示属性名;ref表示引用其他bean的id值-->
        <property name="fruitDAO" ref="fruitDAO"/>
    </bean>
    <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController">
        <property name="fruitService" ref="fruitService"/>
    </bean>
</beans>

上面的标签property是自定义的,name,ref也是自定义的,但我们会赋予他们意义。property这个标签也是一个可以被获取到的字符串,name的值是bean中的一个属性名(这个属性就是依赖的那个类),ref作为key,从beanMap容器中取出对应的实例

按照java解析xml的API的说啊,bean是xml中的节点,声明在bean标签内部的property标签是子节点,节点,子节点在API中都有对应的类

bean+property的结构仿造的是层与层之间的结构,比如说FruitController这个bean,它内部需要用到FruitServiceImpl实例,FruitController这个类将FruitServiceImpl实例作为他的一个属性

property标签中的ref=“fruitService”引用的就是id="fruitService"的bean

同样的,FruitServiceImpl这个类将FruitDAOImpl实例作为他的一个属性,property标签中的ref=“fruitDAO”引用的就是id="fruitDAO"的bean

当然,这种所谓的引用关系程序本身是不知道的,需要程序员自己编写代码实现这一套逻辑,他只是一个xml文件,他什么都做不了的

xml的背景-进一步解析xml配置文件
<sname>jim</sname>

之前,我学到的是sname叫做标签,jim叫做内容

更专业的说法是,<sname></sname>都是元素节点,jim叫做文本节点。节点有对应的类Node,元素也有对应的类叫做Element

<bean id="fruit" class="com.atguigu.fruit.controllers.FruitController">
    <property name="fruitService" ref="fruitService"/>
</bean>

那么这种情况下,bean,property对于他们自己来说都是节点,同时也都是元素节点;在有比较的情况下,property是bean的子节点;我们是从元素节点当中取出他的属性

Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
    Element beanElement = (Element)beanNode ;
    String beanId =  beanElement.getAttribute("id");

上面那段代码,可以看到先从NodeList实例中取出一个Node实例,然后将Node实例强转为Element实例,再从Element实例中取出叫做id的属性值

在IOC层组装bean的依赖关系-赋值
<bean id="fruit" class="com.atguigu.fruit.controllers.FruitController">
    <property name="fruitService" ref="fruitService"/>
</bean>

在上面这个xml配置中,bean依赖里面的property

//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)取出被依赖的实例FruitServiceImpl
(2)取出依赖别人的实例FruitController
(3)重点来了,利用反射机制给类中的属性赋值
(4)property这个标签也是一个可以被获取到的字符串,name的值是bean中的一个属性名(这个属性就是依赖的那个类),ref作为key,从beanMap容器中取出对应的实例

FruitController依赖FruitServiceImpl
FruitController中有一个属性private FruitService fruitService = null ;,刚好是name的值
(5)通过反射,先拿到FruitController的class,然后依据name的值拿到这个属性,然后将这个属性赋值为FruitServiceImpl实例

完整程序

//5.组装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");
        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);
            }
        }
    }
}
新的IOC层

当ioc层独立出来之后,就区别于之前在中央控制器时那样只负责提供控制器实例,从此以后,控制器实例,业务实例,DAO实例都统一由IOC中的beanMap容器进行管理

也就是说各层之间的依赖消失了,他们现在都依赖IOC层,彼此之间依靠IOC层关联。从层层依赖关系变成了一对多的关系

配置文件的作用

时至今日,我们已经遇到过很多需要使用配置文件的地方:
(1)JDBC隐藏第三方API时,在配置文件.properties中记录了四个键值对

user=root
password=123456
url=jdbc:mysql://localhost:13306/test?rewriteBatchedStatements=true
driverClass=com.mysql.jdbc.Driver

(2)使用数据库连接池技术时,也使用了配置文件
(3)在javaweb项目中的WEB-INF/web.xml文件中,配置了servlet mapping,init-param, context-param等
(4)为了创建beanMap容器,创建了一个applicationContext.xml配置文件,用于将一个字符串和其对应的类的地址组成键值对

<bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/>

配置文件里边都是些字符串,像什么fruit,root,jdbc:mysql://localhost:13306/这种字符串还好理解,尤其是什么com.mysql.jdbc.Driver或者com.atguigu.fruit.controllers.FruitController这种字符串,他是一个具体的类在项目中的地址,你会觉得他不只是个字符串,它比普通的字符串有更多的用处

在实践中,我们看到这种描述具体的类在项目中的地址的字符串是用于反射机制

反射机制可以获取到一个类的所有信息,这些信息由Class,Method,Field等这些类进行管理,在类的内部提供了getName方法来获取以字符串形式存在的信息,比如说方法名,属性名,类名,都是以字符串的形式存储在对应类的实例中

总结

中央控制器的作用

经过一通解耦合之后,中央控制器的职能又发生了变化:
(1)继承了ViewBaseServlet,用于thymeleaf渲染
(2)重写init方法,调用父类ViewBaseServlet的init方法,实例化IOC层的BeanFactory用于提供各层需要的实例

public void init() throws ServletException {
    super.init();
    beanFactory = new ClassPathXmlApplicationContext();
}

(3)重写service方法,解析url确定调用控制器的哪一个方法,统一获取参数,视图处理

IOC层的作用

新的IOC层负责:解析applicationContext.xml配置文件,创建beanMap容器,组装bean之间的依赖关系(赋值)

IOC层的结构非常简单:(1)接口BeanFactory,用于定义一些方法(2)接口的实现类,一个作为属性的map容器,一个空参构造器,重写接口中的方法

(1)applicationContext.xml配置文件有两个作用

<beans>
    <bean id="fruitDAO" class="com.atguigu.fruit.dao.impl.FruitDAOImpl"/>
    <bean id="fruitService" class="com.atguigu.fruit.service.impl.FruitServiceImpl">
        <!-- property标签用来表示属性;name表示属性名;ref表示引用其他bean的id值-->
        <property name="fruitDAO" ref="fruitDAO"/>
    </bean>
    <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController">
        <property name="fruitService" ref="fruitService"/>
    </bean>
</beans>

一方面,他为造beanMap容器提供依据,里面存放双列数据,双列数据都是字符串,id是一个字符串,这个字符串是我们会约定好的,他和前端传回来是一致的,class也是一个字符串,他是每一层的对应类的地址,控制器层,业务层,DAO层

另一方面,他还要说明层之间的依赖关系。如果层A依赖层B,那么就在层A的bean里边加上层B的property
这个写法其实是非常清晰的,我们以控制器层依赖业务层为例,property中的name的值是一个字符串,表示控制器层内部有一个属性的属性名就叫做fruitService,ref也是一个字符串,可以通过这个值作为key在beanMap中找到这个属性的值。所以说ref的值和bean中的id的某个值是相等的,因为bean中的id是作为beanMap的key存在

(2)解析applicationContext.xml配制文件。获取配置文件中所有的bean,获取bean中的双列数据,或者说键值对,完成beanMap容器的创建。beanMap中的键值对,key是一个约定好的字符串,value就是其对应的一个实例,每一层对应类的实例

(3)组装bean之间的依赖关系。其实就是解析applicationContext.xml配置文件中带有property的bean。所谓的组装,其实就是进行赋值。比如说,控制器层依赖下层的业务层,在有IOC之前,我们是直接在控制器层中实例化了业务层,将业务层实例作为控制器层的一个属性。但现在是由IOC层利用反射机制为这个属性进行赋值,这个值来自于第一步中的beanMap容器

依赖不会消失,只是进行了转移。一旦我将配置文件删掉,系统就失效了

有了IOC层之后,层与层之间的依赖关系消失了,现在所有层都依赖IOC层。以前,上一层需要下一层提供实例,现在实例统一由IOC提供

回顾

  1. Servlet生命周期中的初始化方法: init() , init(config)
    public void init(ServletConfig config) throws ServletException {
    this.config = config ;
    init();
    }
    因此,如果我们需要在初始化时执行一些自定义的操作,那么我们可以重写无参的init方法。
    我们可以通过getConfig()获取ServletConfig对象
    可以通过config.getInitParameter()获取初始化参数

  2. 通过ServletContext获取配置的上下文参数

  3. MVC : V:view 视图 ; C:Controller 控制器 ; M:Model 模型
    模型有很多种类:数据访问模型(DAO);业务逻辑模型(BO,有的公司叫biz,有的叫service);值对象模型(POJO);数据传输对象(DTO)

  4. IOC
    IOC - 控制反转 / DI - 依赖注入
    控制反转:

    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。这个现象我们称之为控制反转

这是我之前没有想到的,从生命周期的角度来审视IOC层的作用

依赖注入:
1) 之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();
   那么,控制层和service层存在耦合。
2) 之后,我们将代码修改成FruitService fruitService = null ;
   然后,在配置文件中配置:
   <bean id="fruit" class="FruitController">
        <property name="fruitService" ref="fruitService"/>
   </bean>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值