1.再次学习Servlet的初始化方法

  • 最初的做法是: 一个请求对应一个Servlet,这样存在的问题是==servlet太多了==

把一些列的请求都对应一个Servlet, IndexServlet/AddServlet/EditServlet/DelServlet/UpdateServlet ->

合并成==FruitServlet==
通过一个operate的值来决定调用FruitServlet中的哪一个方法
使用的是switch-case

在上一个版本中,Servlet中充斥着大量的switch-case,试想一下,随着我们的项目的业务规模扩大,那么会有很多的Servlet,也就意味着会有很多的switch-case,这是一种代码冗余

因此,我们在servlet中使用了反射技术,我们规定operate的值和方法名一致,那么接收到operate的值是什么就表明我们需要调用对应的方法进行响应,如果找不到对应的方法,则抛异常

在上一个版本中我们使用了反射技术,但是其实还是存在一定的问题:每一个servlet中都有类似的反射技术的代码。因此继续抽取,设计了中央控制器类:

==DispatcherServlet==
DispatcherServlet这个类的工作分为两大部分:

  1. 根据url定位到能够处理这个请求的controller组件:
    1. 从url中提取servletPath : /fruit.do -> fruit
    2. 根据fruit找到对应的组件:FruitController , 这个对应的依据我们存储在applicationContext.xml中
    <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController/>通过DOM技术我们去解析XML文件,在中央控制器中形成一个beanMap容器,用来存放所有的Controller组件

    1. 根据获取到的operate的值定位到我们FruitController中需要调用的方法
  2. 调用Controller组件中的方法:

    1. 获取参数
      获取即将要调用的方法的参数签名信息: Parameter[] parameters = method.getParameters();
      通过parameter.getName()获取参数的名称;
      准备了Object[] parameterValues 这个数组用来存放对应参数的参数值
      另外,我们需要考虑参数的类型问题,需要做类型转化的工作。通过parameter.getType()获取参数的类型

    2. 执行方法
      Object returnObj = method.invoke(controllerBean , parameterValues);

    3. 视图处理

      1
      2
      3
      4
      1. 1. String returnStr = (String)returnObj;
      if(returnStr.startWith("redirect:")){
      ....
      }else if.....

==Servlet的初始化方法==

Servlet生命周期:实例化、初始化、服务、销毁

Servlet中的初始化方法有两个:

init() , init(config)

1
2
3
4
5
6
7
8
9
10
//其中带参数的方法代码如下:
public void init(ServletConfig config) throws ServletException {
this.config = config ;
init();
}

//另外一个无参的init方法如下:
public void init() throws ServletException{

}

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

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

在web.xml文件中配置Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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>
<servlet-mapping>
<servlet-name>Demo01Servlet</servlet-name>
<url-pattern>/demo01</url-pattern>
</servlet-mapping>

==也可以通过注解的方式进行配置==:

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

2、学习Servlet中的ServletContext<context-param>

1、获取ServletContext

在初始化方法中: ServletContxt servletContext = getServletContext();
在服务方法中也可以通过request对象获取,也可以通过session获取:
request.getServletContext(); session.getServletContext()

2、获取初始化值:

servletContext.getInitParameter();

3、什么是业务层(MVC)

  1. Model1和Model2

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

    • pojo/vo : 值对象

      DAO : 数据访问对象 (访问数据库)

      BO : 业务对象

  2. 区分业务对象和数据访问对象:

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

4、IOC

1、耦合/依赖

依赖指的是某某某离不开某某某

在软件系统中,层与层之间是存在依赖的。我们也称之为==耦合==。
我们系统架构或者是设计的一个原则是: 高内聚低耦合。
层内部的组成应该是高度聚合的,而层与层之间的关系应该是低耦合的,最理想的情况0耦合(就是没有耦合)

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

  • 依赖注入

    1. 之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();
      那么,控制层和service层存在耦合。

    2. 之后,我们将代码修改成FruitService fruitService = null ;
      然后,在配置文件中配置:

      1
      2
      3
      <bean id="fruit" class="FruitController">
      <property name="fruitService" ref="fruitService"/>
      </bean>

⭐理解⭐

最先是DispatcherServlet (父类是ViewBaseServlet extends HttpServlet ) (调度器)@WebServlet("*.do") (会接收所有以.do结尾的请求)

接收 来自浏览器的请求(默认是Get方式),

⚠️⚠️⚠️⚠️⚠️ 会先执行init()方法 初始化servlet

可以在init()方法内执行一些准备操作,比如DispatcherServlet 的就是在这里通过初始化beanFactory对象,

new ClassPathXmlApplicationContext();读取==applicationContext.xml==的内容,方便下面直接通过beanFactory依据servletPath获取Controller

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

此时进入Service方法:

Service内执行的内容

  1. 获取operate 以及 fruit (fruit 由 /fruit.do获得)

  2. 通过BeanFactory根据经过处理的furit获得FruitController对象

    Object controllerBeanObj = beanFactory.getBean(servletPath);,

    • 因为通过 ==applicationContext.xml== 的配置 ,fruit 对应 FruitController.java

    • applicationContext.xml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <?xml version="1.0" encoding="utf-8"?>

      <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值(reference : 引用)-->
      <property name="fruitDAO" ref="fruitDAO"/>
      </bean>
      <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController">
      <property name="fruitService" ref="fruitService"/>
      </bean>
      </beans>

    然后通过反射 controllerBeanObj.getClass().getDeclaredMethods(); 获取 operate对应的 controller内的方法,

    此处注意要设置method.setAccessible(true);

    另外,由于为了实现低耦合(层与层之间的关系不那么密切),fruitController 已经无法获得

    • HttpServletRequest request
    • HttpServletResponse response

    因此 ,fruitcontroller内方法执行需要的参数必须直接在==中央调度器DispatcherServlet :service==方法内==直接传递具体的参数==

    • FruitController内的方法如下

      1
      2
      3
      4
      5
      private String edit(Integer fid , HttpServletRequest request)
      private String update(Integer fid ,String fname,Integer price,Integer fcount ,String remark )
      private String del(Integer fid)
      private String add(String fname , Integer price , Integer fcount , String remark )
      private String index(String oper , String keyword , Integer pageNo , HttpServletRequest request )
    • 具体的参数传递的操作如下 (注意 操作执行的前提是==匹配到了执行操作的方法==)

      ==if(operate.equals(method.getName()))==

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      //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 ;
      }
      }

method.invoke(controllerBeanObj,parameterValues);完成方法调用后,设计的 FruitController 的方法会返回需要执行的操作,

Object returnObj = method.invoke(controllerBeanObj,parameterValues);

然后进行视图处理

1
2
3
4
5
6
7
8
9
//3.视图处理
String methodReturnStr = (String)returnObj ;
if(methodReturnStr.startsWith("redirect:")){ //比如: redirect:fruit.do
String redirectStr = methodReturnStr.substring("redirect:".length());
response.sendRedirect(redirectStr);
// 比如返回值是 redirect:fruit.do 就会 重定向,访问fruit.do (其实就是再次执行调度器的Service)
}else{
super.processTemplate(methodReturnStr,request,response); // 比如: "edit"
}

顺序:

  1. DispacherServlet 的init ()
    • 先调用父类的init()
    • 然后beanFactory = new ClassPathXmlApplicationContext();通过ClassPathXmlApplicationContext的构造器读取==applicationContext.xml==配置文件,初始化ClassPathXmlApplicationContext.beanMap
  2. 然后进入service 方法
  3. 通过service方法执行Controller的需要操作的方法
  4. 根据Controller执行的方法的返回值决定下一步
  5. 继续第一步(客户端重定向) or 视图渲染(跳转页面)