SSM框架(Spring + Spring MVC + MyBatis)

作者:神秘网友 发布时间:2020-10-07 19:03:47

SSM框架(Spring + Spring MVC + MyBatis)

SSM框架(Spring + Spring MVC + MyBatis)

SSM框架(Spring + Spring MVC + MyBatis)

  • 一、Mybatis
    • 1. 框架
      • **1.1 生活中的框架**
      • **1.2 程序中框架**:代码的【半成品】
    • 2. mybatis简介
    • 3. ORM简介&mybatis框架对比
      • **3.1 ORM介绍**
      • **3.2 mybatis框架对比**
    • 4. Mybatis之helloworld
      • **4.1 使用所有框架的共同步骤**
      • **4.2 使用Mybatis框架步骤**
    • 5. Mybatis的核心配置文件【重点】
      • 5.1 作用:
      • **5.2 核心配置如下**
    • 6. Mybatis的映射文件
      • **6.1 概述**
      • **6.2 Mybatis映射文件顶级标签**
    • 7. 获取数据库受影响函数
    • 8. 获取主键自增数值[insert|update]
    • 9. Mybatis中参数传递问题【重点】
      • **9.1 单个普通参数**
      • **9.2 多个普通参数**
      • **9.3 命名参数**
      • **9.4 POJO参数**
      • **9.5 Map参数**
      • **9.6 Collectioin|List|Array参数**【了解】
    • 10. 使用resultType的几种情况【查询select的几种情况】
    • 11. 自动映射【resultType】&自定义映射【resultMap】【重点】
      • **11.1 自动映射**【resultType】
      • **11.2 自定义映射**【resultMap】
    • 12. Mybatis中参数获取方式
      • **12.1 语法结构**
      • **12.2 使用场景**
    • 13. MyBatis动态SQL【重点】
      • **13.1 动态SQL简介**
      • **13.2** 动态SQL详解
    • 14. Mybatis中的缓存机制
      • **14.1 为什么使用缓存**
      • **14.2 Mybatis的中两种缓存**
    • 15. Mybatis逆向工程
      • **15.1 逆向工程概述:**
      • **15.2 逆向工程使用步骤**
    • 16. 分页插件
      • **16.1 回顾JavaWeb分页**
      • **16.2 Mybatis中分页插件-PageHelper**
  • 二、Spring
    • 1.Spring简介
    • 2. 搭建Spring框架【Helloworld】
    • 3. Spring中getBean()常用的三种情况
    • 4. IOC概述
    • 5. Spring中为Bean中的属性赋值【依赖注入方式】
      • **5.1 Spring注入方式之Set注入**
      • **5.2 Spring注入方式之构造器注入**
      • **5.3 p名称空间注入**
    • 6. Spring中注入值,支持的数据类型
      • **6.1 字面量**
      • **6.2 引入外部已声明bean**
      • **6.3 级联属性赋值**
      • **6.4 内部bean**
      • **6.5 List\Set\Array**
      • **6.6 Map**
      • **6.7 Util-list**
    • 7. FactoryBean
    • 8. Bean作用域
      • **8.1 四个作用域范围**
      • **8.2 设置bean的作用域**
    • 9. Bean的生命周期
      • **9.1 Bean生命周期**
      • **9.2 后置处理器**
      • **9.3 配置后置处理器,Bean生命周期**
    • 10. Spring管理第三方Bean
      • **10.1 使用Spring管理【com.alibaba.druid.pool.DruidDataSource】**
      • **10.2 引入外部资源文件**
    • 11. 自动装配
      • **11.1 如何实现自动装配**
      • **11.2 自动装配方式**
      • **11.3 建议使用情况**
    • 12. Spring中注解使用
      • **12.1 在框架中为什么使用注解**
      • **12.2 使用注解的准备**
      • **12.3 常用注解**
    • 13. Spring中组件扫描
      • **13.1 基本标签及问题**
    • 14. @Autowired注解【组件装配】
      • **14.1 @Autowired注解的注入方式:反射原理【无构造器&去setXXX()任然可以注入值】**
      • **14.2 @Autowired注解装配原理**
    • 15. AOP准备
      • **15.1 传统实现日志功能不足**
      • **15.2 将非业务功能【日志功能】提取到工具类,动态织入【作用】回到业务代码中。**
      • **15.3 手动搭建代理类**
    • 16. 使用AspectJ
      • **16.1 AspectJ简介**
      • **16.2 AspectJ使用步骤**
    • 17. AOP相关术语
      • **17.1 AOP概述**
      • **17.2 AOP术语**
      • **17.3 AOP术语图解**
    • 18. 通知
      • **18.1 切入点表达式**
      • **18.2 常用通知**
      • **18.3 定义切面优先级**
      • **18.4 基于XML方式配置AOP**
    • 19. JdbcTemplate框架
      • 19.1 概念:
      • **19.2 JdbcTemplate使用基本步骤**
      • **19.3 JdbcTemplate常用方法**
    • 20. Spring中事务管理
      • **20.1 Spring中支持的事务管理**
      • **20.2 使用声明式事务步骤**
      • **20.3 事务属性**
      • **20.4 基于XML方式配置事务**
  • 三、SpringMVC
    • 1. SpringMVC简介
    • 2. 搭建SpringMVC框架步骤
    • 3. @RequestMapping注解
      • **3.1 @RequestMapping书写位置**
      • **3.2 @RequestMapping属性**
    • 4. @RequestMapping支持的Ant风格的URL
    • 5. @PathVariable
    • 6. REST风格编程
      • **6.1 为什么使用REST**
      • **6.2 HiddenHttpMethodFilter过滤器**
    • 7. SpringMVC中处理请求数据
      • **7.1 JavaWeb处理请求数据**
      • **7.2 SpringMVC中处理请求数据概述**
      • **7.3 SpringMVC中处理请求数据方式**
      • **7.4 SpringMVC中解决请求响应乱码问题**
    • 8. SpringMVC中处理响应数据
      • **8.1 回顾JavaWeb如何处理响应**
      • **8.2 SpringMVC中处理响应数据**
      • **8.3 源码解析ModelAndView**
      • **8.4 使用Model&Map作为形参,处理响应数据**
      • 8.5 源码解析SpringMVC处理响应数据,原理
    • 9. 视图与视图解析器
      • **9.1 常用的两个接口**
      • **9.2 JstlView视图对象**
      • **9.3 RedirectView视图对象**
      • **9.4 源码解析视图解析器及视图对象工作原理**
    • 10. 综合练习_Rest风格CRUD
      • **10.1 搭建支持Rest风格的环境**
      • **10.2 Spring表单标签**
      • 10.3 实现删除功能步骤
      • **10.4 扩展**
    • 11. SpringMVC中处理Json数据
      • **11.1 使用SpringMVC解决Json数据问题步骤**
      • **11.2 SpringMVC中处理Json数据详情**
    • 12. 文件上传&文件下载
      • **12.1 文件上传**
      • **12.2 文件下载**
    • 13. SpringMVC中拦截器【Interceptor】
      • **13.1 过滤器【Filter】与拦截器【Interceptor】区别**
      • **13.2 实现拦截器两种方式**
      • **13.3 实现拦截器代码**
      • **13.4 单个Interceptor工作原理**
      • **13.5 多个Interceptor工作原理**
      • **13.6 拦截器中preHandle()方法 return false时的工作原理**
    • 14. SpringMVC中处理异常机制
      • **14.1 SpringMVC中默认异常处理器【DefaultHandlerExceptionResolver】**
      • **14.2 SpringMVC中的SimpleMappingExceptionResolver**
    • 15. SpringMVC最终版工作原理
      • **15.1 扩展三个对象**
    • 16. SSM整合
      • **16.1 SSM整合思路**
      • **16.2 SSM整合步骤**

一、Mybatis

1.1 生活中的框架

  • 买楼房:房屋“半成品”
  • 手抓饼:食物“半成品”

1.2 程序中框架:代码的【半成品】

  • spring:全能型
  • springMVC:servlet半成品
  • mybatis:jdbc[dao层]半成品
  • mybatis前身ibatis,2010.6更名为mybatis
  • MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果
  • MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
  • Mybatis 是一个 半自动的ORM(Object Relation Mapping)框架

3.1 ORM介绍

  • 全称:ORM:Object Relation Mapping【对象 关系 映射】
  • 将java中的对象与数据库中的表,建立关系【映射关系】
  • 可以直接操作java对象,从而影响表中的数据。

3.2 mybatis框架对比

  • mybatis与jdbc对比
    • jdbc:sql语句与java代码相耦合,不利于代码后期维护和扩展。
    • mybatis:实现sql与java代码解耦问题,sql语句书写【映射文件】中。有利于代码后期维护和扩展。
  • mybatis与hibernate对比【ssh = spring + struts2 + hibernate】
    • hibernate全自动ORM框架,底层sql语句无需我们程序员手写。【不利于sql优化】
    • mybatis半自动ORM框架,底层SQL语句需要我们程序员手写。【有利于sql优化】

4.1 使用所有框架的共同步骤

  1. 导入相应jar包
  2. 编写核心配置文件
  3. 使用核心类库

4.2 使用Mybatis框架步骤

  1. 导入相关jar包

    • mybatis-3.5.1.jar

    • mysql-connector-java-5.1.7-bin.jar

  2. 创建核心配置文件【mybatis-config.xml】

  3. 创建数据库&表&pojo

  4. 创建【映射文件】,名称:XXXMapper.xml

    • 映射文件名称与接口名称一致
    • 映射文件中namespace的值与接口的全类名一致
    • 映射文件中标签的id【<select id="">】与接口中方法名一致
    • ResultType属性,需要指定返回值类型的全类名
  5. 通过核心配置文件构建SqlSessionFactory对象,再通过SqlSessionFactory获取SqlSession对象。

  6. 使用SqlSession中的相应方法,操作数据表数据。

    • session.selectOne();
    • session.getMapper();【推荐使用】

5.1 作用:

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。

5.2 核心配置如下

  • configuration(配置):mybatis核心配置文件的根标签。

  • properties(属性):一般将数据源信息提取到外部配置文件中,实现解耦。

<properties resource="基于类路径,引入外部资源文件" url="基于URL【盘符|http://....】,引入外部资源文件">
<!-- <property name="driver" value="com.mysql.jdbc.Driver"/>-->
    </properties>

settings(设置):这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

  • mapUnderscoreToCamelCase:是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。

  • 注意:last_name 只能与 lastName自动映射,不能与其他方式自动映射。

<settings>
<!--        是否开启驼峰命名自动映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
  • typeAliases(类型别名):类型别名可为 Java 类型设置一个缩写名字。
<typeAliases>
<!--        <typeAlias type="com.atguigu.pojo.Employee" alias="employee"/>-->
<!--        为指定包,统一定义别名,默认别名为类名首字母小写形式
			如:com.atguigu.pojo.Employee,别名为:employee
-->
        <package name="com.atguigu.pojo"/>
    </typeAliases>
  • environments(数据库环境配置)
<environments default="development">
        <environment id="development">
<!--            配置事务管理器,最终是要spring【声明式事务管理器】
        this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
-->
            <transactionManager type="JDBC"/>
<!--            配置数据源
                typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
-->
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}"/>
                <property name="url" value="${db.url}"/>
                <property name="username" value="${db.username}"/>
                <property name="password" value="${db.password}"/>
            </dataSource>
        </environment>
    </environments>
  • mappers(映射器):加载映射文件
<mappers>
<!--        <mapper resource="EmployeeMapper.xml"/>-->
<!--        设置指定包下的所有映射文件的路径【当前包下的所有映射文件,统一加载】
            要求:映射文件所在包,必须与接口所在的包名一致。
    -->
        <package name="com.atguigu.mapper"/>
    </mappers>
  • 总结:核心配置文件中的标签的书写顺序规则如下:
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, 
objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, 
environments?, databaseIdProvider?, mappers?)>

6.1 概述

  • MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。
  • MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
  • 如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。

6.2 Mybatis映射文件顶级标签

  • select标签:书写select开头sql语句,用于查询数据
    • 属性
      • id:在命名空间中唯一的标识符,可以被用来引用这条语句。
      • parameterType:参数类型【全类名】,可选的,一般省略。
      • resultType:期望从这条语句中返回结果的类全限定名或别名。【预期结果集返回的全类名】
      • resultMap:
  • insert标签:书写insert开头sql语句,用于添加数据
  • update标签:书写update开头sql语句,用于修改数据
  • delete标签:书写delete开头sql语句,用于删除数据
  • sql
  • cache
  • cache-ref
  • resultMap
  • 直接将Mapper接口方法中的返回值,设置为Integer|Boolean
  • Integer:接收数据库受影响行数
  • Boolean:true,对数据库有影响【受影响行数>=1】;false,对数据库无影响。
  • useGeneratedKeys:令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键。

  • keyProperty:设置当前主键存放对象的目标属性。

  • 代码:

<insert id="insertEmp" useGeneratedKeys="true" keyProperty="id">

9.1 单个普通参数

  • 普通参数:String+基本数据类型+基本数据类型包装类。
  • 任意传递参数【参数名可以任意使用】

9.2 多个普通参数

  • Mybatis 3.4版本

    • mybatis底层会将参数封装为map,map的key是固定值【0,1,2…或 param1,param2,param3,…】
  • Mybatis3.5版本

    • mybatis底层会将参数封装为map,map的key是固定值【arg0,arg1,…或 param1,param2,param3,…】
  • 使用时主要包含以下两种情况

<select id="selectEmpByIdAndName" resultType="employee">
    SELECT id,last_name,email,gender FROM tbl_employee
    WHERE
        id=#{arg0}
    AND
        last_name=#{arg1}
</select>
<select id="selectEmpByIdAndName" resultType="employee">
    SELECT id,last_name,email,gender FROM tbl_employee
    WHERE
        id=#{param1}
    AND
        last_name=#{param2}
</select>

9.3 命名参数

  • mybatis底层会按照命名的参数,作为map的key,进行封装map。

  • 使用情况如下

public Employee selectEmpByNamed(@Param(value="id") Integer id, @Param("name") String name);
<select id="selectEmpByNamed" resultType="employee">
        SELECT id,last_name,email,gender FROM tbl_employee
        WHERE
            id=#{id}	
        AND
            last_name=#{name}
    </select>
<select id="selectEmpByNamed" resultType="employee">
        SELECT id,last_name,email,gender FROM tbl_employee
        WHERE
            id=#{param1}	
        AND
            last_name=#{param2}
    </select>

命名参数底层源码结构

  • MapperMethod源码如下【86行处理命名参数的本质】
} else {
  Object param = method.convertArgsToSqlCommandParam(args); //86行
  result = sqlSession.selectOne(command.getName(), param);
  if (method.returnsOptional()
      && (result == null || !method.getReturnType().equals(result.getClass()))) {
    result = Optional.ofNullable(result);
  }
}
  • ParamNameResolver源码如下【命名参数底层封装map为:ParamMap类型】
public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  if (args == null || paramCount == 0) {
    return null;
  } else if (!hasParamAnnotation && paramCount == 1) {
    return args[names.firstKey()];
  } else {
    final Map<String, Object> param = new ParamMap<>();		//底层map
    int i = 0;
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
      param.put(entry.getValue(), args[entry.getKey()]);
      // add generic param names (param1, param2, ...)
      final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
      // ensure not to overwrite parameter named with @Param
      if (!names.containsValue(genericParamName)) {
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
  }
}

9.4 POJO参数

  • 使用POJO作为参数进行传递,直接使用POJO中的属性传参即可。

  • 代码

<insert id="insertEmp" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO
     tbl_employee(last_name,email,gender)
    VALUES
     (#{lastName},#{email},#{gender})
</insert>

public void insertEmp(Employee employee);

9.5 Map参数

  • 使用Map作为参数进行传递,直接使用map的key传参即可。

  • 代码

<select id="selectEmpByMap" resultType="employee">
        SELECT id,last_name,email,gender FROM tbl_employee
        WHERE
            id=#{id}
        AND
            last_name=#{lastName}
    </select>

//接口
public Employee selectEmpByMap(Map<String,Object> map);

//测试map传递参数
Map<String,Object> map = new HashMap<>();
map.put("id",1);
map.put("lastName","testName");
Employee employee = mapper.selectEmpByMap(map);
System.out.println("employee = " + employee);

9.6 Collectioin|List|Array参数【了解】

  • 使用Collection作为参数,封装map的key为:collection
  • 使用Array作为参数,封装map的key为:array
  • 使用List作为参数,封装map的key为:list和collection

resultType:期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。

  1. 查询单行数据返回单个对象【POJO】

    • 映射文件中的resultType属性设置为:POJO即可。
  2. 查询多行数据返回对象的集合【List】

    • 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。
  3. 查询单行数据返回Map集合【Map<String,Object>】

    • 映射文件中的resultType属性设置为:java.util.Map
<select id="getEmployees" resultType="java.util.Map">
        SELECT
            id,last_name,email,gender
        FROM
            tbl_employee
        WHERE
            id=#{id}
    </select>

public Map<String,Object> getEmployees(Integer id);

Map<String, Object> map = mapper.getEmployees(1);
        System.out.println("map = " + map);
        System.out.println("lastname:" + map.get("last_name"));

4.查询多行数据返回Map集合【Map<Integer,POJO>】

  • 映射文件中的resultType属性设置为:POJO【只要返回值类型中包含POJO,就将resultType设置为POJO】

  • 必须使用@MapKey注解,指定POJO中的哪个属性作为Map的key。

  • 代码

 <select id="getEmployeesReMap" resultType="com.atguigu.pojo.Employee">
        SELECT
            id,last_name,email,gender
        FROM
            tbl_employee
        WHERE
            id=#{id}
    </select>

@MapKey(value="id")
public Map<Integer,Employee> getEmployeesReMap(Integer id);

Map<Integer, Employee> map = mapper.getEmployeesReMap(1);
System.out.println("map = " + map);

11.1 自动映射【resultType】

  • 概述:自动将表中的字段与POJO类中的属性关联映射

  • 条件:

    • SQL语句结果集中的列名与POJO类中的额属性名一致

    • 如果【列名与属性名】不一致,可以使用mapUnderscoreTocamelCase=true设置

      【注意:last_name 只能与 lastName自动映射,不能与其他方式自动映射。】

  • 问题:自动映射不是万能的,以下两种情况自动映射无法解决

    • 结果集中的列名,与POJO中的属性不一致且不满足【驼峰式自动映射的条件】时,无法完成自动映射
      • 如:last_name与lName无法完成自动映射
    • 多表连接查询,如需查询多表中的数据时,无法完成自动映射。
      • 如:SELECT * FROM tbl_employee e,tbl_dept d WHERE e.dept_id=d.id
    • 总结:自动映射【resultType】无法解决问题时,使用自定义映射【resultMap】解决。

11.2 自定义映射【resultMap】

概述:自定义结果集中的列名与POJO中属性的关联关系

  • 级联映射
//POJO代码
private Integer id ;
    private String lastName;
    private String email ;
    private String gender ;
    private Dept dept;
//映射文件代码
<resultMap id="empAndDeptBase" type="employee">
<!--        定义主键列的关联关系-->
        <id column="eid" property="id"></id>
<!--        定义普通列的关联关系-->
        <result column="last_name" property="lastName"></result>
        <result column="email" property="email"></result>
        <result column="gender" property="gender"></result>
<!--        级联映射-->
        <result column="did" property="dept.id"></result>
        <result column="dept_name" property="dept.deptName"></result>
    </resultMap>
    
 <select id="selectEmployeeById" resultMap="empAndDeptBase">
        SELECT e.`id `eid,e.`last_name`,e.`email`,e.`gender`,e.`dept_id`,d.`id` did,d.`dept_name`
        FROM tbl_employee e,tbl_dept d
        WHERE e.`dept_id`=d.id
        AND e.id=#{id}
    </select>
  • association(多对一的表关系)映射,collection(一对多的表关系)

  • association映射

<resultMap id="empAndDeptAssociation" type="employee">
        <id column="eid" property="id"></id>
        <result column="last_name" property="lastName"></result>
        <result column="email" property="email"></result>
        <result column="gender" property="gender"></result>
<!--        使用association关联【在员工中管理部门的关联关系】-->
        <association property="dept" javaType="com.atguigu.pojo.Dept">
            <id column="did" property="id"></id>
            <result column="dept_name" property="deptName"></result>
        </association>
    </resultMap>
  • association高级用法【分步查询】
<resultMap id="empAndDeptAssociationAndStep" type="employee">
    <id column="eid" property="id"></id>
    <result column="last_name" property="lastName"></result>
    <result column="email" property="email"></result>
    <result column="gender" property="gender"></result>
    <!--        使用association关联【在员工中管理部门的关联关系】-->
    <association property="dept" select="com.atguigu.mapper.DeptMapper.selectDeptByDId"
        column="did">
    </association>
</resultMap>
  • 延迟加载【懒加载】:需要数据再加载,如不需要数据,暂时不加载。
  <!-- 开启全局延迟加载,true:开启延迟加载 -->

    <setting name=*"lazyLoadingEnabled"* value=*"true"*/>

    <!-- 设置加载的数据是按需还是全部,false:按需加载 -->

    <setting name=*"aggressiveLazyLoading"* value=*"false"*/>
  • collection映射【一对多】

    • collection基本使用
<resultMap id="deptAndEmpBase" type="dept">
        <id column="did" property="id"></id>
        <result column="dept_name" property="deptName"></result>
<!--        使用collection关联员工-->
        <collection property="list" ofType="com.atguigu.pojo.Employee">
            <id column="eid" property="id"></id>
            <result column="last_name" property="lastName"></result>
            <result column="email" property="email"></result>
            <result column="gender" property="gender"></result>
        </collection>
    </resultMap>
  • collection分步查询
<resultMap id="deptAndEmpStep" type="dept">
    <id column="did" property="id"></id>
    <result column="dept_name" property="deptName"></result>
    <!--        使用collection关联员工-->
    <collection property="list"
    select="com.atguigu.mapper.EmployeeMapper.selectEmployeeByDeptId"
    column="did">
    </collection>
</resultMap>

扩展

  1. 如何实现局部设置懒加载?

    • 在association或collection中添加属性【fetchType】
      • lazy:开启懒加载
      • eager:关闭懒加载【全部加载】
  2. 分步查询,如需多个参数如何设置?

    • 直接封装为map即可

    • 语法:{key=value,key2=value2…}

    • 代码

<select id="selectEmployeeByDeptId" resultMap="empAndDeptAssociation">
        SELECT e.`id` eid,e.`last_name`,e.`email`,e.`gender`,e.`dept_id`,d.`id` did,d.`dept_name`
        FROM tbl_employee e,tbl_dept d
        WHERE e.`dept_id`=d.id
        AND d.id=#{deptId}
    </select>

<resultMap id="deptAndEmpStep" type="dept">
    <id column="did" property="id"></id>
    <result column="dept_name" property="deptName"></result>
    <!--        使用collection关联员工-->
    <collection property="list"
    select="com.atguigu.mapper.EmployeeMapper.selectEmployeeByDeptId"
    column="{deptId=did}"
    fetchType="eager">
    </collection>
</resultMap>

12.1 语法结构

  • #{key}:使用占位符的方式获取参数,底层使用PreparedStatement,相对安全。
  • ${_parameter}:使用字符串拼接的方式获取参数,底层使用Statement,存在SQL注入隐患,相对不安全。

12.2 使用场景

  • 当使用#{key}的方式解决不了的问题时,使用${_parameter}解决。
  • 如:select col1,col2,… from tbl where col=? group by col having ? order by col limit?,?
    • ?所处位置,即为占位符可以解决的问题,使用#{key}
    • 如其他位置需要传递参数,使用${_parameter}【一般会结合命名参数传参】

13.1 动态SQL简介

  • 动态SQL指:SQL语句可动态变化。

  • 动态 SQL是MyBatis强大特性之一。极大的简化我们拼装SQL的操作

  • MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作

    • OGNL( Object Graph Navigation Language )对象图导航语言,这是一种强大的

      表达式语言。

13.2 动态SQL详解

  • <where> : 添加where关键字,去掉where关键字后面第一个and|or

  • <if test=“OGNL表达式语言”>:流程控制基本用法

  • <trim>:为sql语句中添加前缀后缀或删除前缀后缀

    • prefix:添加前缀
    • prefixOverrides:删除前缀
    • suffix:添加后缀
    • suffixOverrides:删除后缀
  • <set>:在SQL修改语句中,添加set关键字,去掉SQL中多出的一个【,】。

  • <choose>:主要用于流程控制分支结构。与jstl中的choose用法一致。

  • <foreach>

    • jstl:<foreach var=“临时变量” item=“需要迭代集合”>

    • 映射文件:<foreach item=“临时变量” collection=“需要迭代集合”>

    • foreach应用场景

迭代数据

/**
 * 循环遍历员工信息【查询员工id[1,2,3,4,5]】
 * sql:  select * from tbl_employee where id in(1,2,3,4,5)
 */
public List<Employee> selectEmployeeByIds(@Param("ids") List<Integer> ids);
    
    <select id="selectEmployeeByIds" resultType="employee">
        SELECT id,last_name,email,gender,dept_id
        FROM tbl_employee
        -- in(1,2,3,4,5)  foreach collection="collection" item="id" separator="," open="(" close=")"
        <where>
            id in
            <foreach collection="ids" item="id" separator="," open="(" close=")">
                #{id}
            </foreach>
        </where>
    </select>

批处理新增信息

注意:如需批处理新增数据时,需要设置数据库url属性,支持多条Sql执行:allowMultiQueries=true

db.url=jdbc:mysql://localhost:3306/db0621?allowMultiQueries=true
  • 方式一
public void batchInsertEmp(@Param("emps") List<Employee> emps);

<insert id="batchInsertEmp">
        <foreach collection="emps" item="emp">
            INSERT INTO
                tbl_employee(last_name,email,gender)
            VALUES
                (#{emp.lastName},#{emp.email},#{emp.gender});
        </foreach>
    </insert>

方式二

public void batchInsertEmp2(List<Employee> emps);

<insert id="batchInsertEmp2">
        INSERT INTO
            tbl_employee(last_name,email,gender)
        VALUES
            <foreach collection="list" item="emp" separator=",">
                (#{emp.lastName},#{emp.email},#{emp.gender})
            </foreach>
</insert>
  • sql片段
<!--提取整个SQL语句-->
    <sql id="select_emp">
        SELECT id,last_name,email,gender,dept_id
        FROM tbl_employee
    </sql>
<!--提取部分列名-->
    <sql id="select_col">
        id,last_name,email,gender,dept_id
    </sql>

<select id="selectEmpByOpr" resultType="employee">
        <include refid="select_emp"></include>
--         WHERE 1=1
        <where>
                <if test="id!=null">
                    AND id=#{id}
                </if>
                <if test="lastName!=null &amp;&amp; lastName!=&quot;&quot; ">
                    AND last_name=#{lastName}
                </if>
                <if test="email!=null and !&quot;&quot;.equals(email)">
                    AND email=#{email}
                </if>
                <if test="gender!=null">
                    AND gender=#{gender}
                </if>
        </where>
    </select>

<select id="selectEmployeeByIds" resultType="employee">
        SELECT <include refid="select_col"></include>-- id,last_name,email,gender,dept_id
        FROM tbl_employee
        -- in(1,2,3,4,5)  foreach collection="collection" item="id" separator="," open="(" close=")"
        <where>
            id in
                <foreach collection="ids" item="id" separator="," open="(" close=")">
                    #{id}
                </foreach>
        </where>
    </select>

14.1 为什么使用缓存

  • 生活中:缓存音频、视频好处:省钱、速度较快。
  • 程序中:使用缓存好处:节约资源、提高程序效率。

14.2 Mybatis的中两种缓存

  • 一级缓存【sqlSession级别缓存|本地缓存】

    • 缓存机制如下:

      • 基于相同SqlSession,多次查询相同数据,每次查询都会优先从缓存中获取数据。
        1. 如果缓存中没有数据时,则从数据库中获取数据,之后,将数据存放到一级缓存中
        2. 如果缓存中存在数据时,直接从缓存中获取数据,无需从数据库中检索数据。
    • 注意:一级缓存默认开启,且不能关闭。但可以使用clearCache()方法清空缓存。

    • 一级缓存失效的四种情况

      1. 不同的SqlSession对应不同的一级缓存

      2. 同一个SqlSession但是查询条件不同

      3. 同一个SqlSession两次查询期间手动清空了缓存【clearCache()】

      4. 同一个SqlSession两次查询期间执行了任何一次增删改操作

      • 当执行增删改操作时,底层默认清空缓存。
  • 二级缓存【namespace级别缓存|sqlSessionFactoty级别缓存】

    • 默认关闭,使用二级缓存之前,需要手动开启二级缓存
    • 使用二级缓存步骤
      1. 全局配置文件中开启二级缓存
      2. 需要使用二级缓存的映射文件处使用cache配置缓存
      3. 注意:POJO需要实现Serializable接口
    • 二级缓存相关属性【<cache>标签中的属性】
      • eviction:设置缓存的回收策略
        • LRU:最近最少使用的先回收
        • FIFO【first in first out】:先进先出
      • flushInternal:刷新的时间间隔,单位毫秒。
      • size:设置缓存的最大对象数量【正整数】
      • readOnly:设置缓存是否为只读【true|false】
      • type:引入第三方缓存类库。
    • 二级缓存的缓存机制
      • 基于相同的SqlSessionFactory,多次查询相同数据,优先从二级缓存中获取数据。
        • 如果二级缓存无数据
          • 优先从一级缓存中获取数据
            • 一级缓存无数据:从数据库中获取数据。【默认先缓存到一级缓存中】
            • 一级缓存有数据:直接获取数据即可。
          • 提交或关闭sqlSession时,将一级缓存中的数据,存放到二级缓存中。
        • 如果二级缓存有数据:则无需进行后续操作【一级缓存|数据库】
  • Mybatis支持的第三方缓存【EhCache】

    • EhCache:进程级缓存框架

    • EhCache优势:可以将缓存的数据,存放到本地磁盘中。

    • EhCache使用步骤

1.导入jar包

2.编写核心配置文件

3.在映射文件中设置

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

4.必须开启二级缓存

15.1 逆向工程概述:

  • MyBatis Generator: 简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。

  • 支持基本的增删改查,以及QBC(Query By Criteria)风格的条件查询。

  • 但是表连接、存储过程等这些复杂sql的定义需要我们手工编写

  • 资源地址

  • 官方文档地址

    http://www.mybatis.org/generator/

  • 官方工程地址

    https://github.com/mybatis/generator/releases

15.2 逆向工程使用步骤

  1. 导入jar包:mybatis-generator-core-1.3.2.jar

  2. 编写配置文件

    • 编写位置在当前项目下即可

    • 文件内容

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--    context
            id:context唯一标识,一般设置相关的数据库表
            targetRuntime:设置反向生成的目标策略
                * MyBatis3:生成QBC(query by criteria)风格CRUD
                * MyBatis3Simple:生成基本的CRUD
-->
    <context id="mysqlTables" targetRuntime="MyBatis3">
<!--        设置数据库的反向生成策略-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/db0621"
                        userId="root"
                        password="root">
        </jdbcConnection>
<!--        设置pojo【javabean】的反向生成策略
                targetPackage:POJO存放的目标package
                targetProject:POJO存放的目标工程【Project/src】
-->
        <javaModelGenerator targetPackage="com.atguigu.pojo" targetProject=".\src">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
<!--        设置映射文件的反向生成策略-->
        <sqlMapGenerator targetPackage="com.atguigu.mapper"  targetProject=".\conf">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
<!--        设置Mapper接口的反向生成策略-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mapper"  targetProject=".\src">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
<!--        设置POJO类与对应表的反向生成策略
                tableName:设置表名
                domainObjectName:设置反向生成POJO名
-->
        <table tableName="tbl_employee" domainObjectName="Employee" ></table>
        <table tableName="tbl_dept" domainObjectName="Dept" ></table>
    </context>
</generatorConfiguration>

3.运行相应生成器代码,实现逆向工程。

try {
    List<String> warnings = new ArrayList<String>();
    boolean overwrite = true;
    File configFile = new File("mbg.xml");
    ConfigurationParser cp = new ConfigurationParser(warnings);
    Configuration config = cp.parseConfiguration(configFile);
    DefaultShellCallback callback = new DefaultShellCallback(overwrite);
    MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
    myBatisGenerator.generate(null);
} catch (Exception e) {
    e.printStackTrace();
}

16.1 回顾JavaWeb分页

  • 业务bean【Page】
    • page中的5个属性:当前页码、总页码、总数据条数、每页显示数量、当前页显示的数据集合【5/49】

16.2 Mybatis中分页插件-PageHelper

  • PageHelper是MyBatis中非常方便的第三方分页插件

  • PageHelper使用步骤

    1. 导入2个jar包

      • pagehelper-x.x.x.jar
      • jsqlparser-x.x.x.jar
    2. 在mybatis核心配置文件中【mybatis-config.xml】,添加插件。

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

注意:<plugins>标签的位置必须在<environments>标签上面
3. 使用核心类库

  • 使用PageHelper中的startPage()方法,开启分页功能
  • 使用PageInfo将查询的数据,封装到PageInfo中。

PageHelper核心类库的使用代码

SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
//开启分页
Page<Object> page = PageHelper.startPage(1, 2);
//测试qbc风格查询
EmployeeExample ee = new EmployeeExample();
//创建条件对象
EmployeeExample.Criteria criteria = ee.createCriteria();
List<Employee> employees = mapper.selectByExample(ee);
for (Employee employee : employees) {
    System.out.println("employee = " + employee);
}
/**
 * 分页业务逻辑
 *      【1】2345
 *      1【2】345
 *      12【3】45
 *      23【4】56
 *      34【5】67
 *      45【6】78
 */
//将查询后的结果employees,封装PageInfo中
PageInfo<Employee> pageInfo = new PageInfo<>(employees,5);
System.out.println("当前页码:" + pageInfo.getPageNum());
System.out.println("总页码:" + pageInfo.getPages());
System.out.println("总数据条数:" + pageInfo.getTotal());
System.out.println("每页显示条数:" + pageInfo.getPageSize());

System.out.println("是否为第一页:" + pageInfo.isIsFirstPage());
System.out.println("是否为最后一页:" + pageInfo.isIsLastPage());
System.out.println("是否有上一页:" + pageInfo.isHasPreviousPage());
System.out.println("是否有下一页:" + pageInfo.isHasNextPage());

//获取分页业务逻辑
int[] nums = pageInfo.getNavigatepageNums();
for (int num : nums) {
    System.out.println("num = " + num);
}

二、Spring

  • Spring官网:http://spring.io
  • Spring是一个开源框架【开放源代码】
  • Spring是一个IOC【DI】和AOP容器框架
  • Spring是一个大的管家【管理对象】
  • Spring特性
    • 一站式:Spring可以解决数据访问层、业务逻辑层、表示层【各个分层】的问题
    • 非侵入式:使用Spring框架,无需实现接口或继承类。
      • Servlet是侵入式【Web组件】:使用组件必须实现某个接口或基础某个类。
    • 组件化:即插即用。
    • 控制反转【依赖注入】:IOC
    • 面向切面编程设计:AOP

2.1 搭建框架步骤

  • 导入jar包
  • 编写核心配置文件
  • 使用核心类库

2.2 搭建Spring框架

  1. 导入5个jar包

    • Spring自身JAR包:spring-framework-5.2.5.RELEASE\libs目录下
      • spring-beans-5.2.5.RELEASE.jar
      • spring-context-5.2.5.RELE2ASE.jar
      • spring-core-5.2.5.RELEASE.jar
      • spring-expression-5.2.5.RELEASE.jar
    • commons-logging-1.1.3.jar
  2. 编写核心配置文件

    • 配置文件名称,可以任意定义。建议:applicationContext.xml或beans.xml

    • 配置文件位置:类路径下即可。

    • 代码

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    装配Emp对象
        bean标签使用
            id:装配bean的唯一标识
            class:设置装配bean的全类名
            子标签:property[注入属性]
                name:设置属性名称
                value:设置属性值
-->
    <bean id="empOne" class="com.atguigu.pojo.Emp">
        <property name="id" value="1001"></property>
        <property name="empName" value="zhangsan"></property>
    </bean>

    <bean id="empTwo" class="com.atguigu.pojo.Emp">
        <property name="id" value="1002"></property>
        <property name="empName" value="lisi"></property>
    </bean>
</beans>

使用核心类库【ApplicationContext容器对象】

//之前使用对象的方式:        Emp emp = new Emp();  强引用【不会被jvm回收】
//学完spring后,使用对象的正确方式,如下:
//获取ApplicationContext容器对象
ApplicationContext context =
        new ClassPathXmlApplicationContext("applicationContext.xml");

//通过容器对象获取Emp
//Emp emp = (Emp)context.getBean("empOne");
//Emp emp2 = (Emp)context.getBean("empOne");
//boolean yOn = emp==emp2;
//System.out.println("emp = " + emp);
// System.out.println("emp2 = " + emp2);
//System.out.println("yOn = " + yOn);

//getBean(Class)
//Emp emp = context.getBean(Emp.class);
//System.out.println("emp = " + emp);
Emp empOne = context.getBean("empTwo", Emp.class);
System.out.println("empOne = " + empOne);
  • getBean(“beanId”):默认获取Object类型,需要强制类型转换才可以使用。

  • getBean(Class clz):如容器中装配了相同类型的bean时,会报错

org.springframework.beans.factory.
NoUniqueBeanDefinitionException: 
No qualifying bean of type 'com.atguigu.pojo.Emp' available:
expected single matching bean but found 2: empOne,empTwo
  • getBean(“beanId”,Class clz):通过beanId和Class获取对象【推荐使用】。
  • IOC(Inversion of Control)【DI】:反转控制
    • 反转对象的控制权及对象获取方式
      • 传统方式: 我想吃饭 我需要买菜做饭
      • 反转控制: 我想吃饭 饭来张口
  • DI(Dependency Injection):依赖注入
  • IOC接口及类的结构图,如下所示
  • SSM框架(Spring + Spring MVC + MyBatis)

Bean标签作用:装配Bean对象

? id:bean唯一标识

? name:bean标识(不唯一,不建议使用)

? class:管理POJO的全类名

5.1 Spring注入方式之Set注入

  • 通过bean标签的子标签<property>实现

  • 代码

<bean id="dept1" class="com.atguigu.pojo.Dept">
    <property name="id" value="1001"></property>
    <property name="deptName" value="研发部门"></property>
</bean>

5.2 Spring注入方式之构造器注入

  • 通过bean标签中的子标签实现

  • 代码

<bean id="empTwo" class="com.atguigu.pojo.Emp">
		<constructor-arg value="1002"></constructor-arg>
        <constructor-arg value="李老师"></constructor-arg>
        <constructor-arg value="男"></constructor-arg>
        <constructor-arg value="18"></constructor-arg>
        <constructor-arg value="1000"></constructor-arg>
</bean>

5.3 p名称空间注入

  • 在beans标签中添加约束:xmlns:p=“http://www.springframework.org/schema/p”

  • p名称空间本质使用set方法注入。

  • 代码

<bean id="emp5" class="com.atguigu.pojo.Emp"
      p:id="1005" p:empName="sunlaoshi" p:gender="女" p:age="17" p:salary="1000">
</bean>

6.1 字面量

  1. 可以使用字符串表示的值,可以通过value属性或value子节点的方式指定

  2. 基本数据类型及其封装类、String等类型都可以采取字面值注入的方式

  3. 若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来

<bean id="empOne" class="com.atguigu.pojo.Emp">
        <property name="id">
            <value>1001</value>
        </property>
        <property name="empName">
            <value><![CDATA[<zhangsan>]]></value>
        </property>
    </bean>

6.2 引入外部已声明bean

6.3 级联属性赋值

<!--    定义dept-->
    <bean id="dept1" class="com.atguigu.pojo.Dept">
        <property name="id" value="1001"></property>
        <property name="deptName" value="研发部门"></property>
    </bean>
<!--    给bean的级联属性赋值-->
    <bean id="empIoc1" class="com.atguigu.pojo.Emp">
        <property name="id" value="2001"></property>
        <property name="empName" value="ywl"></property>
        <property name="gender" value="男"></property>
        <property name="age" value="17"></property>
        <property name="salary" value="15"></property>
<!--        给bean的级联属性赋值-->
        <property name="dept" ref="dept1"></property>
        <property name="dept.id" value="1002"></property>
        <property name="dept.deptName" value="教学部门"></property>
    </bean>

6.4 内部bean

<!--    内部bean-->
    <bean id="empIoc2" class="com.atguigu.pojo.Emp">
        <property name="id" value="2002"></property>
        <property name="empName" value="ywl2"></property>
        <property name="gender" value="男"></property>
        <property name="age" value="18"></property>
        <property name="salary" value="17"></property>
        <!--        给bean的级联属性赋值-->
        <property name="dept">
            <bean class="com.atguigu.pojo.Dept">
                <property name="id" value="1003"></property>
                <property name="deptName" value="财务部门"></property>
            </bean>
        </property>
    </bean>

6.5 List\Set\Array

<!--    装配list\Map等类型-->
    <bean id="dept4" class="com.atguigu.pojo.Dept">
        <property name="id" value="1004"></property>
        <property name="deptName" value="后勤部门"></property>
        <property name="emps" >
            <list>
                <bean class="com.atguigu.pojo.Emp">
                    <property name="id" value=""></property>
                </bean>
                <ref bean="empIoc1"></ref>
                <ref bean="empIoc2"></ref>
            </list>
<!--            <array></array>-->
<!--            <set></set>-->
        </property>
    </bean>

6.6 Map

<bean id="dept5" class="com.atguigu.pojo.Dept">
        <property name="id" value="1005"></property>
        <property name="deptName" value="后厨部门"></property>
        <property name="map">
            <map>
                <entry>
                    <key>
                        <value>ywl</value>
                    </key>
                    <ref bean="empIoc1"></ref>
<!--                    <bean></bean>-->
                </entry>
                <entry>
                    <key>
                        <value>ywl2</value>
                    </key>
                    <ref bean="empIoc2"></ref>
                </entry>
            </map>
        </property>
    </bean>

6.7 Util-list

<!--    提取共用率高元素-->
    <util:list id="utilList1">
        <ref bean="empIoc1"></ref>
        <ref bean="empIoc2"></ref>
    </util:list>
<!--    装配list\Map等类型-->
    <bean id="dept4" class="com.atguigu.pojo.Dept">
        <property name="id" value="1004"></property>
        <property name="deptName" value="后勤部门"></property>
        <property name="emps" ref="utilList1">
        </property>
    </bean>

Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。

FactoryBean作用

? 如在开发中,需要我们程序员参与到【对象】的创建时,怎么办?此时使用FactoryBean。

  • FactoryBean的实现代码如下所示:
public class MyFactoryBean implements FactoryBean<Dept> {

//    返回pojo对象
    @Override
    public Dept getObject() throws Exception {
        Dept dept = new Dept(2001,"测试部门");
        return dept;
    }

//    返回对象的Class
    @Override
    public Class<?> getObjectType() {
        return Dept.class;
    }

}
<bean id="myFacctoryBean" class="com.atguigu.factory.MyFactoryBean"></bean>
 @Test
    public void testmyFacctoryBean(){
        //获取容器对象
        ApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext_factorybean.xml");
        //错误的:org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'myFacctoryBean' is expected to be of type 'com.atguigu.factory.MyFactoryBean' but was actually of type 'com.atguigu.pojo.Dept'
//        MyFactoryBean myFacctoryBean = context.getBean("myFacctoryBean", MyFactoryBean.class);
//        System.out.println("myFacctoryBean = " + myFacctoryBean);

        Dept dept = context.getBean("myFacctoryBean", Dept.class);
        System.out.println("dept = " + dept);
    }

8.1 四个作用域范围

  • singleton:【默认值】单例【单实例:在当前环境中,只有一个实例对象】
    • 在创建容器对象时,创建pojo对象。
  • prototype:多例【单实例:在当前环境中,可以有多个实例对象】
    • 在调用getBean()方法时,创建pojo对象。
  • request:请求域 request
  • session:会话域 session

8.2 设置bean的作用域

  • 在bean标签中使用scope属性进行设置【如java工程,scope只要两个属性值(singleton,prototype)】

9.1 Bean生命周期

? ① 通过构造器或工厂方法创建bean实例

? ② 为bean的属性设置值和对其他bean的引用

? ③ 调用bean的初始化方法

? ④ bean可以使用了

? ⑤ 当容器关闭时,调用bean的销毁方法

注意:在配置bean时,通过init-method和destroy-method 属性为bean指定初始化和销毁方法

9.2 后置处理器

在初始化前后,可以为bean再次加工。

  • 实现后置处理器步骤

    1. 实现BeanPostProcessor接口

    2. 重写里面两个方法,具体代码如下

/**
 * 在初始化方法执行【之前】执行。
 * @param bean:被处理的目标bean,处理后的bean,必须返回;否则报错。
 * @param beanName: 代表beanId
 * @return
 * @throws BeansException
 */
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("在初始化方法执行【之前】执行。处理[email protected]@@");
    return bean;
}

/**
 * 在初始化方法执行【之后】执行。
 * @param bean
 * @param beanName
 * @return
 * @throws BeansException
 */
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("在初始化方法执行【之后】执行。处理bean!!!");
    return bean;
}
  1. 装配后置处理器

9.3 配置后置处理器,Bean生命周期

? ① 通过构造器或工厂方法创建bean实例

? ② 为bean的属性设置值和对其他bean的引用

? 执行后置处理器中的postProcessBeforeInitialization()

? ③ 调用bean的初始化方法

? 执行后置处理器中的postProcessAfterInitialization()

? ④ bean可以使用了

? ⑤ 当容器关闭时,调用bean的销毁方法

10.1 使用Spring管理【com.alibaba.druid.pool.DruidDataSource】

10.2 引入外部资源文件

  • 外部资源文件db.properties
#key=value
db.url=jdbc:mysql://localhost:3306/db0621
db.driver=com.mysql.jdbc.Driver
db.username=root
db.password=root
  • applicationContext.xml文件
<!--    引入外部资源文件
            classpath:引入当前模块类路径下的某资源
            classpath*:引入所有模块类路径下的某资源
-->
    <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--    装配第三方bean【德鲁伊数据源】-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${db.driver}"></property>
        <property name="url" value="${db.url}"></property>
        <property name="username" value="${db.username}"></property>
        <property name="password" value="${db.password}"></property>
    </bean>
  • 测试类中代码
/**
 * 测试,使用Spring管理第三方bean
 */
@Test
public void testdataSource(){
    //创建容器对象
    ApplicationContext context =
            new ClassPathXmlApplicationContext("applicationContext_dataSource.xml");
    try {
        DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
        System.out.println("dataSource = " + dataSource);
        DruidPooledConnection connection = dataSource.getConnection();
        System.out.println("connection = " + connection);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

spring中支持两种装配方式

  1. 手动装配:使用value或ref手动装配
  2. 自动装配:无需使用value或ref,就可以自动装配。

11.1 如何实现自动装配

  • 在bean中添加autowire属性实现自动装配,可选值:byName|byType
  • 注意:自动装配属性的数据类型,只能是[非字面量]值。[字面量]值不能自动装配。

11.2 自动装配方式

  • byName【按名称自动装配】
    • 如类中的属性与IOC容器中的beanId一致,则自动装配成功。如不一致,不会报错,装配null值。
  • byType【按类型自动装配】
    • 如类中的属性类型与IOC容器中的class值【唯一匹配】,则装配成功
    • 不唯一匹配
      • 没有匹配
        • 不报错,装配null值。
      • 匹配多个
        • 报错:expected single matching bean but found 2: address2,address

11.3 建议使用情况

  • byName或byType只能使用一种,我们期望最后两种结合使用。
  • 使用byName或byType不能为类中的局部属性自动装配,只能将类中的所有属性自动装配。
  • 以上两种情况均不建议使用,建议使用注解方式。

12.1 在框架中为什么使用注解

  • 使用配置文件优化或减少java代码
  • 使用注解优化或简化配置文件代码

12.2 使用注解的准备

  • 必须在原有JAR包组合的基础上再导入一个:spring-aop-5.2.5.RELEASE.jar
  • 在配置文件中,添加组件扫描器
    • <context:component-scan base-package=“被扫描的包,及其子包”/>

12.3 常用注解

  1. 普通组件:@Component

? 标识一个受Spring IOC容器管理的组件

  1. 持久化层组件:@Repository

标识一个受Spring IOC容器管理的持久化层组件

  1. 业务逻辑层组件:@Service

标识一个受Spring IOC容器管理的业务逻辑层组件

  1. 表述层控制器组件:@Controller

标识一个受Spring IOC容器管理的表述层控制器组件

  1. 以上注解,解决使用注解管理bean的问题(不能管理bean中的属性)
    2. 组件命名【beanId】规则
    * 默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id
    * 使用注解value属性,手动设置bean的id

    • 如果当前注解,只使用一个属性,value关键字可以省略
    1. spring底层并没区分这四个注解的能力(使用四个注解与使用一个注解的效果一样)
      但建议区别使用,因为提高代码的可读性。

13.1 基本标签及问题

<context:component-scan base-package="当前包及其子包均会被扫描"/>
  • 如: annotation中共100个子包,但其中bean子包不需要被扫描,问:怎么办?
    annotation中共100个子包,但其中只要bean子包需要被扫描,问:怎么办?

  • 解决方案

    • 包含扫描:<context:include-filter></context:include-filter>

      • 代码
<context:component-scan base-package="com.atguigu.annotation" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
  • 排除扫描:<context:exclude-filter></context:exclude-filter>
  • 代码
<context:component-scan base-package="com.atguigu.annotation">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:exclude-filter type="assignable" expression="com.atguigu.annotation.service.UserServiceImpl"/>
</context:component-scan>

type属性:

* assignable:通过指定bean的全类名进行匹配
* annotation:通过指定bean的注解进行匹配【推荐使用】

注意:如使用包含过滤扫描,需为组件扫描添加属性:use-default-filters=“false”,
关闭默认组件扫描

14.1 @Autowired注解的注入方式:反射原理【无构造器&去setXXX()任然可以注入值】

14.2 @Autowired注解装配原理

  • 优先使用byType匹配方式,如果【唯一匹配】,则匹配成功。
  • 如果没有【唯一匹配】
    • 再按照byName进行唯一筛选,如筛选成功,则匹配成功。
    • 按byName未唯一筛选成功,则按byType的方式【作出返回结果】
  • required:设置当前被注解【@Autowired】的属性是否必须自动装配
    • 默认值:true
    • 可以设置为false【但一般不设置false】
  • @Qualifier:按照名称匹配【一般结合@Autowire的注解使用】

15.1 传统实现日志功能不足

  • 日志功能与业务功能耦合度高
  • 不利于程序的扩展性,维护性。

15.2 将非业务功能【日志功能】提取到工具类,动态织入【作用】回到业务代码中。

15.3 手动搭建代理类

public class CalcImplProxy {
    /**
     * 实现动态代理步骤
     * 1. 定义被代理对象【目标对象】
     * 2. 获取代理对象【中介】
     * 3. 有参构造器
     * 实现动态代理核心对象
     *  Proxy:代理类基类【类似java中Object】
     *  InvocationHandler:实现代理对象目标方法【动态织入【作用】回到业务代码中】
     */
    //1. 定义被代理对象【目标对象】
    private Object target;
    //3. 有参构造器
    public CalcImplProxy(Object target){
        this.target = target;
    }
    //2. 获取代理对象【中介】
    public Object getProxyObj(){
        Object obj = null;
        //类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //获取目标对象的实现接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        obj = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            //动态织入【作用】回到业务代码中
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();   //获取方法名称
                //织入方法之前日志
                MyLogging.methodBefore(methodName,args);
                //执行代理对象方法【代理目标对象,相当于执行加法或减法等方法】
                Object rs = method.invoke(target, args);
                //织入方法之后日志
                MyLogging.methodAfter(methodName,rs);
                return rs;
            }
        });
        return obj;
    }
}

16.1 AspectJ简介

  • AspectJ:Java社区里最完整最流行的AOP框架。
  • 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。

16.2 AspectJ使用步骤

  1. 导入jar包

    • l com.springsource.net.sf.cglib-2.2.0.jar

      l com.springsource.org.aopalliance-1.0.0.jar

      l com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

      l spring-aop-5.2.5.RELEASE.jar

      l spring-aspects-5.2.5.RELEASE.jar

  2. 在配置文件中添加:<aop:aspectj-autoproxy/>【开启AspectJ动态代理】

  3. 添加注解

    1. 将所有类使用@Component注解装配到IOC容器
    2. 将非业务类使用@Aspect注解标识为切面
    3. 在指定方法上使用各种通知【前置通知、后置通知等】
  4. 测试

  5. 示例代码

@Component      //将bean装配到IOC容器中
@Aspect         //再将bean定义成切面
public class MyLogging {
    @Before(value="execution(* *.*(..))")
    public void methodBefore(JoinPoint jp){
        //获取方法名字
        String methodName = jp.getSignature().getName();
        //获取参数
        Object[] args = jp.getArgs();
        System.out.println("==》日志:执行"+methodName+"之前,参数为,args:"+ Arrays.toString(args));
    }
    @After(value="execution(public double com.atguigu.aop.CalcImpl.add(double,double))")
    public void methodAfter(JoinPoint jp){
        String methodName = jp.getSignature().getName();
        System.out.println("==》日志:执行"+methodName+"之后,结果为,rs:");
    }
}

17.1 AOP概述

  • OOP【面向对象的编程设计】

  • AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传

    统 OOP(Object-Oriented Programming,面向对象编程)的补充。

  • 总结:

    • OOP:纵向继承机制
    • AOP:横向提取机制【将非核心业务,先横向提取,再动态作用回业务代码中】

17.2 AOP术语

  • 横切关注点:非核心业务(日志功能)
  • 切面:非核心业务提取类中,当前类称之为:切面。(MyLogging)
  • 通知:将非核心业务,提取到切面后,横切关注点也被称之为:通知。
  • 目标:需要被代理的类,称之为目标类(CalcImpl)
  • 代理:自定义的CalcImplProxy类称之为代理类,
    通过该类中的方法【getProxyObject()】获取的对象,称之为代理对象。
  • 连接点:需要被通知的位置(需要将非核心业务作用回核心业务中的位置)
  • 切入点:被通知后的连接点,也被称之为:切入点。

17.3 AOP术语图解

SSM框架(Spring + Spring MVC + MyBatis)

18.1 切入点表达式

  • 语法:execution([权限修饰符] [返回值类型] [简单类名/全类名] 方法名)

    • 如:
@After(value="execution(public double com.atguigu.aop.CalcImpl.add(double,double))")
  • 常用符号

    1. 【*】
      • 【*】可以代表任意权限修饰符和返回值类型
      • 【*】可以代表任意类名【类全路径】
      • 【*】可以代表任意方法名
    2. 【…】
      • 代表任意参数【数据类型任意&数量任意】
  • 重用切入点表达式

    • 提取切入点表达式
@Pointcut(value = "execution(* com.atguigu.aop.*.*(..))")
public void myPointCut(){}

重用切入点表达式

@Before(value="myPointCut()")
public void methodBefore(JoinPoint jp){}

18.2 常用通知

  • 前置通知

    • 语法:@Before()

    • 执行时机:被通知方法执行之前执行。

    • 代码

/**
 * 前置通知
 * @param jp
 */
@Before(value="myPointCut()")
public void methodBefore(JoinPoint jp){
    //获取方法名字
    String methodName = jp.getSignature().getName();
    //获取参数
    Object[] args = jp.getArgs();
    System.out.println("==》日志:执行"+methodName+"之前,参数为,args:"+ Arrays.toString(args));
}
  • 后置通知

  • 语法:@After()

  • 执行时机:被通知方法执行之后执行。【如出现异常,会执行】

  • 代码

/**
 * 后置通知
 * @param jp
 */
@After(value="myPointCut()")
public void methodAfter(JoinPoint jp){
    String methodName = jp.getSignature().getName();
    //获取参数
    Object[] args = jp.getArgs();
    System.out.println("==》日志:执行"+methodName+"之后,参数为,args:" + Arrays.toString(args));
}
  • 返回通知

  • 语法:@AfterReturning()

  • 执行时机:被通知方法执行【返回结果】后执行。【如出现异常,不会执行】

  • 代码

/**
 * 返回通知
 */
@AfterReturning(value="myPointCut()", returning = "data")
public void methodAfterReturning(JoinPoint jp,Object data){
    //获取方法名称
    String methodName = jp.getSignature().getName();
    System.out.println("==》日志:执行"+methodName+"返回结果之后执行,结果为,data:" + data);
}
  • 注意:返回通知中的returning属性值,与参数中接收结果的参数值,必须一致。

  • 异常通知

  • 语法:@AfterThrowing()

  • 执行时机:被通知方法【出现异常】后执行。【如无异常,不会执行】

  • 代码

/**
 * 异常通知
 */
@AfterThrowing(value = "myPointCut()", throwing = "ex")
public void methodAfterThrowing(JoinPoint jp,Exception ex){
    //获取方法名称
    String methodName = jp.getSignature().getName();
    System.out.println("==》日志:执行"+methodName+"出现异常之后执行,异常类型为,ex:" + ex);
}
  • 注意:异常通知中的throwing属性值,与参数中的接收异常的参数值,必须一致。

  • 环绕通知

  • 语法:@Around()

  • 作用:整合以上四种通知【前置、后置、返回、异常通知】

  • 代码

/**
 * 环绕通知
 */
@Around(value="myPointCut()")
public Object methodAround(ProceedingJoinPoint pjp){
    Object obj = null;
    String methodName = pjp.getSignature().getName();
    Object[] args = pjp.getArgs();
    try {
        //前置通知
        System.out.println("==》日志:执行"+methodName+"之后,参数为,args:" + Arrays.toString(args));
        //执行目标对象的指定方法【加减乘除方法】
        obj = pjp.proceed();
        //返回通知
        System.out.println("==》日志:执行"+methodName+"返回结果之后执行,结果为,data:" + obj);
    } catch (Throwable throwable) {
        throwable.printStackTrace();
        //异常通知
        System.out.println("==》日志:执行"+methodName+"出现异常之后执行,异常类型为,ex:" + throwable);
    } finally {
        //后置通知
        System.out.println("==》日志:执行"+methodName+"之后,参数为,args:" + Arrays.toString(args));
    }
    return obj;
}

18.3 定义切面优先级

  • 语法:@Order(value=数值)
  • 注意:
    • 数值是正整数【>=0】
    • 数值越小,优先级越高

18.4 基于XML方式配置AOP

<!--    装配目标bean-->
    <bean id="calcImpl" class="com.atguigu.xmlaop.CalcImpl"></bean>
<!--    装配切面MyLogging-->
    <bean id="myLogging" class="com.atguigu.xmlaop.MyLogging"></bean>
<!--    装配切面MyValidata-->
    <bean id="myValidate" class="com.atguigu.xmlaop.MyValidate"></bean>
<!-- 配置AOP
        定义切入点表达式
        定义切面
        定义通知
-->
    <aop:config>
        <!--定义切入点表达式-->
        <aop:pointcut id="myPointcut" expression="execution(* com.atguigu.xmlaop.*.*(..))"/>
        <!--定义MyLogging切面-->
        <aop:aspect id="log" ref="myLogging" order="2">
            <!--定义前置通知-->
            <aop:before method="methodBefore" pointcut-ref="myPointcut"></aop:before>
            <!--定义后置通知-->
            <aop:after method="methodAfter" pointcut-ref="myPointcut"></aop:after>
            <!--定义返回通知-->
            <aop:after-returning method="methodAfterReturning" returning="data" pointcut-ref="myPointcut"></aop:after-returning>
            <!--定义异常通知-->
            <aop:after-throwing method="methodAfterThrowing" throwing="ex" pointcut-ref="myPointcut"></aop:after-throwing>
        </aop:aspect>
        <aop:aspect id="val" ref="myValidate" order="1">
            <aop:before method="dataVal" pointcut-ref="myPointcut"></aop:before>
        </aop:aspect>
    </aop:config>

19.1 概念:

Spring提供的一个持久化框架(模板),其核心对象JdbcTemplate(类似之前使用的DBUtils)

19.2 JdbcTemplate使用基本步骤

  1. 导入10个jar包

    1. IOC容器所需要的JAR包

    ? commons-logging-1.1.1.jar

    ? spring-beans-5.2.5.RELEASE.jar

    ? spring-context-5.2.5.RELEASE.jar

    ? spring-core-5.2.5.RELEASE.jar

    ? spring-expression-5.2.5.RELEASE.jar

    1. JdbcTemplate所需要的JAR包

    ? spring-jdbc-5.2.5.RELEASE.jar

    ? spring-orm-5.2.5.RELEASE.jar

    ? spring-tx-5.2.5.RELEASE.jar

    1. 数据库驱动和数据源

    ? druid-1.1.9.jar

    ? mysql-connector-java-5.1.7-bin.jar

  2. 编写配置文件

    • db.properties文件
db.url=jdbc:mysql://localhost:3306/db0621
db.driver=com.mysql.jdbc.Driver
db.username=root
db.password=root

applicationContext.xml文件

<!-- 加载外部资源文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--    装配数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="${db.url}"></property>
    <property name="driverClassName" value="${db.driver}"></property>
    <property name="username" value="${db.username}"></property>
    <property name="password" value="${db.password}"></property>
</bean>
<!--    装配JdbcTempate
            1. 先装配数据源
            2. 加载外部资源文件
-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

使用JdbcTemplate中的核心方法,实现CRUD操作

public void testJdbcTemplate(){
    JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
    System.out.println("jdbcTemplate = " + jdbcTemplate);
}

19.3 JdbcTemplate常用方法

  • update()

    • 增删改通用方法

    • 常用构造器update(String sql,Object… args)

    • 代码

public void testUpdate(){
    String sql = "update tbl_dept set dept_name=? where id=?";
    int num = jdbcTemplate.update(sql, "福利部门", 5);
    System.out.println("num = " + num);
}
  • batchUpdate()

    • 批处理增删改通用方法

    • 常用构造器batchUpdate(String sql,List<Object[]>)

    • 代码

public void testBatchupdate(){
    String sql = "insert into tbl_dept(dept_name) values(?)";
	//定义参数
    List<Object[]> list = new ArrayList<>();
    list.add(new Object[]{"保安部门"});
    list.add(new Object[]{"秘书部门"});
    list.add(new Object[]{"有关部门"});

    jdbcTemplate.batchUpdate(sql,list);
}
  • queryForObject()

    • 查询单个数据通用方法【单个值&单个对象】

      • 单个值:queryForObject(String sql,Class clz)

      • 单个对象:queryForObject(String sql,RowMapper rm);

      • 代码

/**
 * 获取部门的个数
 */
@org.junit.Test
public void testQueryForObject(){
    String sql = "select count(*) from tbl_dept";
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
    System.out.println("count = " + count);
}

/**
* 通过部门id获取部门信息
*/
 @org.junit.Test
 public void testQueryForObjectDept(){
        String sql = "select id,dept_name from tbl_dept where id=?";
        //定义RowMapper[将对象中的属性与表中的字段映射]
        RowMapper<Dept> rowMapper = new BeanPropertyRowMapper<>(Dept.class);
        Dept dept = jdbcTemplate.queryForObject(sql, rowMapper, 7);
        System.out.println("dept = " + dept);
        //错误的
		//Dept dept = jdbcTemplate.queryForObject(sql, Dept.class,1);
		//System.out.println("dept = " + dept);
}
  • query()

    • 查询多个对象通用方法

    • 常用构造器query(String sql,RowMapper rm)

    • 代码

public void testQuery(){
    String sql = "select id,dept_name from tbl_dept where dept_name=?";
    //定义rowMapper
    RowMapper<Dept> rowMapper = new BeanPropertyRowMapper<>(Dept.class);
    List<Dept> list = jdbcTemplate.query(sql, rowMapper,"有关部门");
    for (Dept dept : list) {
        System.out.println("dept = " + dept);
    }
}

回顾事务

  1. 事务四大特性【特征】
   - 原子性
   - 一致性
   - 隔离性
   - 持久性
  2. 事务三种操作
   - 开启事务:connection.setAutoCommit(false)
   - 提交事务:connection.commit()
   - 回滚事务:connection.rollback()

去结账业务【1. 生成订单 2. 生成订单详情 3.更改Book库存和销量 4. 清空购物车】Filter

20.1 Spring中支持的事务管理

  • 编程式事务管理

    ? ①获取数据库连接Connection对象

    ? ②取消事务的自动提交

    ? ③执行操作

    ? ④正常完成操作时手动提交事务

    ? ⑤执行失败时回滚事务

    ? ⑥关闭相关资源

  • 声明式事务管理

    • 使用Spring AOP思想【框架】,管理事务【先将事务管理代码从业务代码中提取,再动态织入回到业务代码中】

20.2 使用声明式事务步骤

装配事务管理器【DataSourceTransactionManager】

<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

开启事务管理

<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

在需要事务方法上,添加注解:@Transactional

@Transactional
public void purchase(String username, String isbn) {}

20.3 事务属性

  • propagation【事务传播行为】:一个事务调用另一个事务时,此时当前事务如何执行事务机制。

    • REQUIRED【0】:如当前存在事务,就按照当前事务执行。如没有事务,就新建一个事务执行【必须以事务的方式执行】
      REQUIRES_NEW【3】:无论之前是否有事务[a],均新建事务[b]执行。如之前有事务a,将之前事务a挂起,先执行当前新建事务b,当前新建事务b执行结束后,将之前的事务a继续执行。
  • isolation【事务隔离级别】

    • 读未提交【1】:不足:脏读
    • 读已提交【2】:不足:不可重复读
    • 可重复读【4】:不足:幻读
    • 串行化【8】:不足:效率低
  • timeout【设置在指定毫秒后,强制事务回滚】

  • readOnly【设置事务是否只读】,一般查询时设置事务为只读。

  • rollbackFor|rollbackForClassName【设置回滚的异常类型】

  • noRollbackFor|noRollbackForClassName【设置不回滚的异常类型】

20.4 基于XML方式配置事务

<!-- 配置事务切面 -->
	<aop:config>
		<aop:pointcut 
			expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))" 
			id="txPointCut"/>
		<!-- 将切入点表达式和事务属性配置关联到一起 -->
		<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
	</aop:config>
	
	<!-- 配置基于XML的声明式事务  -->
	<tx:advice id="myTx" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 设置具体方法的事务属性 -->
			<tx:method name="find*" read-only="true"/>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="purchase" 
				isolation="READ_COMMITTED" 
	no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"
				propagation="REQUIRES_NEW"
				read-only="false"
				timeout="10"/>
		</tx:attributes>
	</tx:advice>

三、SpringMVC

  • SpringMVC是Spring技术体系之一
  • Spring 为展现层提供的基于 MVC 设计理念的优秀的 Web 框架,是目前最主流的MVC 框架之一
  • Spring MVC 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无须实现任何接口。
  1. 搭建动态java工程【JavaWeb 2.5】

  2. 搭建SpringMVC环境【导入jar包】

    • spring-aop-5.2.5.RELEASE.jar

      spring-beans-5.2.5.RELEASE.jar

      spring-context-5.2.5.RELEASE.jar

      spring-core-5.2.5.RELEASE.jar

      spring-expression-5.2.5.RELEASE.jar

      commons-logging-1.1.3.jar

      spring-web-5.2.5.RELEASE.jar

      spring-webmvc-5.2.5.RELEASE.jar

  3. 编写核心配置文件【applicationContext-mvc.xml或springmvc.xml】

  • 装配扫描器
<context:component-scan base-package="com.atguigu"/>
  • 装配视图解析器
  <!-- 装配视图解析器
        将逻辑视图名 转换为  物理视图名
        success            /WEB-INF/pages/success.jsp
-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--        指定前缀-->
        <property name="prefix" value="/WEB-INF/pages/"></property>
<!--        指定后缀-->
        <property name="suffix" value=".jsp"></property>
    </bean>

4.在web.xml中配置前端控制器【DispatcherServlet】

  • 在前端控制器中管理SpringMVC容器对象

  • 在启动服务器时,创建DispatcherServlet

  • 在url配置中,设置【/】

  • 代码

<!--    配置前端控制器【DispatcherServlet】-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--        加载springMVC核心配置文件【创建容器对象】-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
<!--        启动服务器时,创建当前DispatcherServlet
                index:正整数,设置创建Servlet优先级
                数值越小,优先级越高。
-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
<!--        所有请求,交个请求处理器
                /* :当前项目下的所有路径
                /:除了【*.jsp】之外的所有路径
-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

5.编写请求处理器【@Controller】

//标识当前类是一个【请求处理器】,注意:SpringMVC标识的请求处理器,必须使用@Controller
@Controller
public class HelloController {
    @RequestMapping(value="/hello")
    public String hello(){
        System.out.println("==>HelloController->hello()!!!");
        //逻辑视图名
        return "success";
    }
}

6.测试:超链接发送请求,跳转/WEB-INF/pages/success.jsp

3.1 @RequestMapping书写位置

@Target({ElementType.TYPE, ElementType.METHOD})
  • 书写在class上面,不可单独使用
  • 书写在method上面,可单独使用
  • 如果两处位置同时书写,先请求class上面路径,再请求method上面路径

3.2 @RequestMapping属性

  • value:为指定的方法映射URL路径【使用URL路径,访问指定方法】
  • path:与value作用一致【spring5之后新属性】
  • method:为指定的方法设置请求方式【GET|POST|PUT|DELETE】
    • 如未按指定请求方式,请求资源。会报错:405
    • 如未指定请求方式,默认支持GET&POST…请求
  • params:为指定的方法设置请求参数【请求时必须携带指定参数,才可以访问指定方法】
    • 未携带指定参数,无法请求指定方法。会报错404 | 400
  • headers:为指定的方法设置请求头信息【请求时必须携带指定请求头信息,才可以访问指定方法】
    • 如未携带指定请求头信息,无法请求指定方法。会报错404
  • ?:匹配文件名中的任意 一个字符

  • * :匹配文件名中的任意字符任意数量

  • **:** 匹配多层路径,任意字符任意数量

  • 代码

@RequestMapping(value = "/testAnt/?/**")
public String testAnt(){
    System.out.println("==>DemoController->testAnt()!!!");
    return "success";
}
  • 作用:获取URL中的占位符数值。【通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中

  • @PathVariable属性

    • value:指定占位符名称
    • name:与value属性作用一致
    • required:默认值true
  • 代码:

<a href="${pageContext.request.contextPath}/testPathVar/1001/ywl">testAnt</a>
@RequestMapping(value = "testPathVar/{id}/{name}")
public String testPathVar(@PathVariable(value = "id") Integer id,@PathVariable("name")String name){
    System.out.println("==>DemoController->id:"+id+",name:"+name);
    return "success";
}

注意:如果传参时的数据类型不匹配,会以警告的方式报错:400:nested exception is java.lang.NumberFormatException

6.1 为什么使用REST

  • 传统CRUD方式【User】

    ? URL 请求方式

    • 增 /addUser POST
    • 删 /deleteUserById?id=1001 GET
    • 改 /updateUser POST
    • 查 /getUserById?id=1001 GET
  • 使用REST风格CRUD

    • 增 /user POST
    • 删 /user/1001 DELETE
    • 改 /user PUT
    • 查 /user/1001 GET
  • 总结

    • 提高网站的排名
    • 有利于第三方平台对接
    • 符合HTTP协议底层设计原理

6.2 HiddenHttpMethodFilter过滤器

  • 使用HiddenHttpMethodFilter作用:解决DELETE和PUT请求方式问题

  • 使用步骤

    • 注册HiddenHttpMethodFilter
    • 必须是POST请求方式
    • 必须传参,参数名:_method【请求方式】
  • 源码解析

private String methodParam = "_method";

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    HttpServletRequest requestToUse = request;
    if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
        String paramValue = request.getParameter(this.methodParam);
        if (StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            if (ALLOWED_METHODS.contains(method)) {
                requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            }
        }
    }

    filterChain.doFilter((ServletRequest)requestToUse, response);
}

 static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }
    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;

        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }
        public String getMethod() {
            return this.method;
        }
    }

  • 如使用Tomcat8.0及以上版本,需要在跳转的目标页面【success.jsp】中,添加如下配置

因为Tomcat8.0默认解析jsp只能处理GET&POST&HEAD。如需要支持DELETE&PUT请求方式,需要在jsp页面中有处理异常能力。所以:添加如下配置【isErrorPage=“true”,jsp中才会存在exception内置对象】

<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>

7.1 JavaWeb处理请求数据

  • request【HttpServletRequest】
  • 作用:
    • 获取请求参数:request.getParameter()
    • 获取URL信息:request.getSchema() request.getServName() request.getServerPort() request.getContextPath()
    • 获取请求头信息:request.getHeader()
    • 转发:request.getRequestDispatcherServlet().forward(request,response);
    • 域对象:request.setAttribute()

7.2 SpringMVC中处理请求数据概述

  • Spring MVC 框架会将 HTTP 请求的信息绑定到相应的方法入参中,并根据方法的返回值类型做出相应的后续处理。
  • 必要时可以对方法及方法入参标注相应的注解( @PathVariable 、@RequestParam、@RequestHeader 等)

7.3 SpringMVC中处理请求数据方式

  • 默认方式

    • SpringMVC默认会将实参与方法签名中的形参名称一致的参数,进行自动匹配【自动入参】。

      如名称不一致,会默认将null值注入。所以建议使用包装类型【如基本数据类型被注入null值,会报错500】

  • @RequestParam注解

    如实参与方法签名中的形参名称不一致时,还需要入参时,使用@RequestParam注解

    • 属性
      • name:设置指定的实参名称,绑定到当前参数。【自定入参】
      • value:与name属性作用一致
      • required:设置当前参数是否可选,默认true【不可选、必须入参】
    • 一旦添加@RequestParam注解,默认如未设置参数时,会报错:400
  • @RequestHeader()注解

    • 作用:获取请求头信息
    • 属性
      • name:设置获取请求头名称
      • value:与name属性作用一致
      • required:设置当前请求头是否可选,默认true【不可选】
  • @CookieValue注解

    • 作用:通过cookie的name获取cookie的value
  • 使用POJO入参

    • 注意:参数名与pojo中的属性名设置一致,就可以自动入参。【支持级联方式入参】

    • 代码:

<form action="${pageContext.request.contextPath}/saveUser" method="post">
    userId:<input type="text" name="id"><br>
    userName:<input type="text" name="username"><br>
    addId:<input type="text" name="address.id"><br>
    addCity:<input type="text" name="address.city"><br>
  <input type="submit" value="提交">
</form>
class User
private Integer id;
private String username;
private Address address;

class Address
private Integer id;
private String city;

class controller
@RequestMapping("/saveUser")
public String saveUser(User user){
    System.out.println("user = " + user);
    return "success";
}

使用原生ServletAPI

  • 直接将原生对象,入参即可

  • 代码:

@RequestMapping("/servletApi")
public String servletApi(HttpServletRequest request, HttpServletResponse response){
    String gender = request.getParameter("gender");
    System.out.println("gender = " + gender);
    return "success";
}

7.4 SpringMVC中解决请求响应乱码问题

  • JavaWeb如何解决请求&响应乱码

    • 三行代码解决
      • 请求乱码
        • GET请求
          • URIEncoding=“UTF-8”
        • POST请求
          • request.setCharacterEncoding(“UTF-8”)
      • 响应乱码
        • response.setCharacterEncoding(“UTF-8”);
        • response.setContentType(“text/html;charset=UTF-8”);
  • SpringMVC解决方案:CharacterEncodingFilter

    • 使用步骤

      1. 注册Filter【必须注册在第一个Filter位置,因为springMVC会识别第一个Filter的字符集,存放到缓存中,以后再使用字符集问题,会从缓存中获取。】
      2. 设置初始化参数:encoding=UTF-8
      3. 设置初始化参数:
        • forceRequestEncoding=true;
        • forceResponseEncoding=true;
    • 源码

private String encoding;
private boolean forceRequestEncoding;
private boolean forceResponseEncoding;
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String encoding = this.getEncoding();
        if (encoding != null) {
            if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
                request.setCharacterEncoding(encoding);
            }

            if (this.isForceResponseEncoding()) {
                response.setCharacterEncoding(encoding);
            }
        }

        filterChain.doFilter(request, response);
    }
  • 使用CharacterEncodingFilter代码
<!--    处理字符集过滤器,必须注册在第一个Filter的位置-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--        设置初始化参数:encoding=UTF-8-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
<!--        设置初始化参数-->
        <init-param>
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

8.1 回顾JavaWeb如何处理响应

数据【模型】:使用域对象【HttpServletRequest request, HttpSession session,ServletContext application】

路径【视图】:转发|重定向

8.2 SpringMVC中处理响应数据

  • ModelAndView【使用ModelAndView对象作为方法的返回值类型,处理响应数据&视图信息】
  • Map 或 Model【使用Map或Model作为方法形参,处理响应数据】

8.3 源码解析ModelAndView

/** View instance or view name String. */
//视图对象或视图名称
private Object view;	
public void setViewName(@Nullable String viewName) {
		this.view = viewName;
}
public String getViewName() {
		return (this.view instanceof String ? (String) this.view : null);
}

//模型数据【ModelMap->LinkedHashMap】
private ModelMap model;
//为模型对象设置数据
public ModelAndView addObject(String attributeName, @Nullable Object attributeValue) {
		getModelMap().addAttribute(attributeName, attributeValue);
		return this;
	}
//获取模型对象中的数据
public Map<String, Object> getModel() {
		return getModelMap();
	}
protected Map<String, Object> getModelInternal() {
		return this.model;
	}
public ModelMap getModelMap() {
		if (this.model == null) {
			this.model = new ModelMap();
		}
		return this.model;
	}
  • 总结

    • 处理数据
//添加数据
mav.addObject("msg","haha");
//获取数据
mav.getModel()
mav.getModelInternal()
mav.getModelMap()
  • 处理视图
//设置视图信息
mav.setViewName("list");
//获取视图信息
mav.getViewName()

8.4 使用Model&Map作为形参,处理响应数据

@RequestMapping(value = "/resDataModelOrMap")
  public String resDataModelOrMap(/*Map<String,Object> map*/Model model){

      //org.springframework.validation.support.BindingAwareModelMap : ModelMap
//        System.out.println("==> " + map.getClass().getName());
//        //将数据存放map中
//        map.put("msg","hehe");

        //org.springframework.validation.support.BindingAwareModelMap
        System.out.println("==> " + model.getClass().getName());
        //测试Model
        model.addAttribute("msg","model:hehe");
        return "list";
    }

8.5 源码解析SpringMVC处理响应数据,原理

InternalResourceView

  • 142行代码:将数据存放到request域中
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);

protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {
		model.forEach((name, value) -> {
			if (value != null) {
				request.setAttribute(name, value);
			}
			else {
				request.removeAttribute(name);
			}
		});
	}

171行代码:执行转发

RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);//151行
rd.forward(request, response);//171

9.1 常用的两个接口

  • View视图接口

    • 作用:
      1. 将数据存放到域中
      2. 路径跳转【转发|重定向】
    • 实现类
      • InternalResourceView:转发视图
      • JstlView:转发视图
      • RedirectView:重定向视图
  • ViewResolver:视图解析器

    • 作用:将视图对象从ModelAndView中解析出来
    • 实现类:InternalResourceViewResolver
  • 无论控制器中的方法返回值类型是String或ModelAndView以及其他,SpringMVC都会统一封装成ModelAndView进行处理。

9.2 JstlView视图对象

  • 环境中配置Jstl支持,就会自动通过视图解析器解析为JstlView视图对象。
  • 配置Jstl环境
    1. 导入2个jar包
    2. 使用指令标签引入jstl标签库

9.3 RedirectView视图对象

转发:request.getRequestDispatcher(“url”).forward(request,response);

重定向:response.sendRedirect(“url”);

转发与重定向区别?

转发地址栏不变,重定向地址栏改变

转发携带request域对象,重定向不能。

转发浏览器发送一次请求,重定向浏览器发送两次请求。

转发可以访问WEB-INF目录下的资源,重定向不能。【WEB-INF属于web应用的私密目录,服务器可以访问,浏览器不能直接访问。】

  • 使用重定向跳转路径时,通过视图解析器自动解析为RedirectView视图对象。

  • SpringMVC中使用重定向的语法

    • @RequestMapping(value = "/testRedirect")
      public String testRedirectMethod(){
          System.out.println("==>testRedirectMethod()###");
          return "redirect:/demo/a.jsp";
      }
      
    • 必须以【redirect:】开头【以redirect:开头,才可以进入if结构,从而创建RedirectView视图对象】

    • 路径中必须以【/】来头,路径中不能省略后缀名。【因为RedirectView视图对象使用绝对路径】

9.4 源码解析视图解析器及视图对象工作原理

  • JstlView视图对象【无源码】

    UrlBasedViewResolver类,553行代码如下

//使用反射原理,创建视图对象[最终调用BeanUtils类113行代码]
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);

BeanUtils类113行代码如下:

//使用反射原理,创建视图对象
return ctor.newInstance(argsWithDefaultValues); 

UrlBasedViewResolver类,554行代码如下

//视图解析器,将逻辑视图名,解析为物理视图名
view.setUrl(getPrefix() + viewName + getSuffix());

RedirectView源码解析
UrlBasedViewResolver类,476行代码如下

//REDIRECT_URL_PREFIX
public static final String REDIRECT_URL_PREFIX = "redirect:";
//viewName="redirect:/demo/a.jsp"
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
    //redirectUrl="/demo/a.jsp"
   String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
   RedirectView view = new RedirectView(redirectUrl,
         isRedirectContextRelative(), isRedirectHttp10Compatible());
   String[] hosts = getRedirectHosts();
   if (hosts != null) {
      view.setHosts(hosts);
   }
   return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
}

10.1 搭建支持Rest风格的环境

  1. 导入15个jar包
  2. 编写springmvc.xml核心配置文件
    • 装配组件扫描器
    • 装配视图解析器
  3. 在web.xml中配置
    • 注册CharacterEncodingFilter过滤器,解决请求&响应乱码问题【必须放在第一个过滤器位置】
    • 注册HiddenHttpMethodFilter过滤器,支持PUT&DELETE提交方式
    • 前端控制器
  4. 编写请求处理器【Controller】,后续开发

10.2 Spring表单标签

  • 为什么使用Spring表单标签

    • 简化表单标签代码
    • 便于数据回显
  • 使用Spring表单标签

    • 导入jar包

    • 引入标签库

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

注意:使用spring表单标签,自带【数据回显】功能,就必须指定回显数据对象。如未指定回显数据对象,会报错:500:java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name ‘command’ available as request attribute

  • 解决方案
//方式一:默认对象key,command
//跳转页面之前,为command准备数据
map.put("command",new Employee());

//方式二:可以使用modelAttribute属性,指定回显对象key
 <form:form action="${pageContext.request.contextPath}/emps" method="post"       modelAttribute="employee"></form:form>
     
map.put("employee",employee);

10.3 实现删除功能步骤

  1. 单击超链接,触发单击事件
  2. 获取超链接href的属性值
  3. 将获取的href属性值,赋值给表单【以DELETE方式发送请求】的action属性。
  4. 提交表单
  5. 取消超链接的默认行为【return false】

10.4 扩展

  • 静态资源加载问题

    • 默认静态资源由tomcat中提供的<servlet-name>default</servlet-name>处理。

    • tomcat->conf->web.xml中部分代码如下

<servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    • 发现问题:解决静态资源加载问题的servlet【default】,配置的url-pattern与springMVC中的前端控制器的url-pattern是一致,springMVC的配置优先级高于服务器配置的优先级。

      所以:解决静态资源加载问题的servlet失效。

  • 解决静态资源加载问题

    • 如加载静态资源出现无法加载问题,需要做如下配置【springmvc.xml】
<!--    设置默认请求处理器为default这个Servlet-->
    <mvc:default-servlet-handler default-servlet-name="default"/>
<!--上面设置完成后,会出现前端控制器失效情况,所以添加如下配置:默认依然优先使用前端控制器,如果映射url不存在时,再交给default默认Servlet-->
    <mvc:annotation-driven/>

总结

  • 静态资源默认不允许存放到WEB-INF目录下,否则无法加载。

  • 如需静态资源必须添加到WEB-INF下,需使用如下配置,映射WEB-INF下资源

<mvc:resources mapping="/static/js/**" location="/WEB-INF/static/js/"></mvc:resources>

<script type="text/javascript" src="${pageContext.request.contextPath}/static/js/jquery-1.7.2.js"></script>

JavaWeb处理Json

  • json是一个js对象 ,eg: var jsonObj = {key:value,key2:value2…}
  • 主要作用:数据交换格式
  • 使用Gson对象处理JsonObj与JsonString转换问题

jQuery中的ajax

  • 语法

    $.ajax({

    ? url:“请求url地址”,

    ? type:“请求方式”,

    ? data:“请求参数”,

    ? dataType:“服务器预期返回的数据类型【xml|json|jsonp|text】”,

    ? success: function(){}, //请求成功时回调函数

    ? error:function(){} //请求失败时的回调函数

? });

11.1 使用SpringMVC解决Json数据问题步骤

  1. 导入jar包

    • jackson-annotations-2.10.3.jar

      jackson-core-2.10.3.jar

      jackson-databind-2.10.3.jar

  2. 将需要转换为json格式的数据,作为方法的返回值即可。

  3. 在处理器的指定方法上,添加注解@ResponseBody

11.2 SpringMVC中处理Json数据详情

  • 服务器端将json数据传递到浏览器端
@ResponseBody
@RequestMapping(value="/ajaxJson")
public Collection<Employee> ajaxJson(){
    Collection<Employee> all = employeeDao.getAll();
    return all;
}
$("#ajaxTest").click(function () {
    //使用ajax请求&处理json数据
    $.ajax({
        url:"${pageContext.request.contextPath}/ajaxJson",
        type:"GET",
        dataType:"json",
        // dataType:"text",
        success:function (data) {
            // alert(data);
            for(var i=0;i<data.length;i++){
                alert("lastName:"+data[i].lastName+",gender:"+data[i].gender);
            }
        },
        error:function () {
            alert("请求失败!");
        }
    });
});
@ResponseBody
@RequestMapping(value="/toJson")
public Student tojson(@RequestBody Student student){
    System.out.println("student = " + student);
    return student;
}

SpringMVC支持Json格式数据的处理问题【底层原理】

  • 因为RequestMappingHandlerAdapter 适配器中包含MappingJackson2HttpMessageConverter消息 转换器。本质因为MappingJackson2HttpMessageConverter消息转换器,才可以处理json数据。
  • 使用<mvc:annotation-driven/>装配RequestMappingHandlerAdapter 适配器。

12.1 文件上传

  • springMVC中处理文件上传,基于javaWeb阶段commons组件进行简化。

  • 实现文件上传步骤

    1. 导入2个jar包
      • commons-fileupload-1.2.1.jar
      • commons-io-1.4.jar
    2. 装配CommonsMultipartResolver工具类
      • bean的id必须是multipartResolver
      • 可以继续为bean注入参数,设置文件上传约束,如字符集、文件上传大小限制等问题
    3. 使用入参后的MultipartFile中的transferTo()方法实现文件上传
  • 实现文件上传代码

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"></property>
    </bean>
@RequestMapping(value="/testUpload")
public String fileUploadMethod(@RequestParam("desc") String desc,
                               @RequestParam("file")MultipartFile multipartFile,
                               HttpSession session){
    System.out.println("desc = " + desc + ", multipartFile = " + multipartFile + ", session = " + session);
    try {
        //获取upload的真实路径
        String realPath = session.getServletContext().getRealPath("/upload");
        //如upload目录不存在,就新建一个upload目录
        File f = new File(realPath);
        if(!f.exists()){
            f.mkdir();
        }
        //获取文件名
        String filename = multipartFile.getOriginalFilename();
        File file = new File(realPath+File.separator+filename);
        //实现文件上传
        multipartFile.transferTo(file);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return "fileuploadsuccess";
}

12.2 文件下载

  • 实现文件下载步骤

    1. 准备下载资源文件
    2. 直接实现文件下载【在处理文件下载的方法中,将方法的返回值类型设置为:ResponseEntity即可。】
      • ResponseEntity:封装了响应行、响应头、响应体的信息。
  • 实现文件下载代码

<h2>0621资源库</h2>
<a href="${pageContext.request.contextPath}/filedownload?filename=commons-io-1.4.jar">commons-io-1.4.jar</a><br>
<a href="${pageContext.request.contextPath}/filedownload?filename=hz.jpg">hz.jpg</a><br>
@RequestMapping(value="/filedownload")
    public ResponseEntity<byte[]> fileDownMethod(HttpServletRequest request,
                                                 String filename){
        ResponseEntity<byte[]> responseEntity = null;
        try {
            //以绝对路径的方式,获取流资源
            InputStream resourceAsStream = request.getServletContext().getResourceAsStream("/download/" + filename);
            if(resourceAsStream!=null){
                //将流资源转换为字节资源【响应体-文件下载资源】
                byte[] bytes = IOUtils.toByteArray(resourceAsStream);
                //设置响应头【设置当前文件为附件,通知浏览器下载,别打开】
                MultiValueMap<String, String> headers = new HttpHeaders();
                headers.add("Content-Disposition","attachment;filename="+filename);
//            HttpHeaders headerss = new HttpHeaders();

                // public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {
                responseEntity = new ResponseEntity<>(bytes,headers, HttpStatus.OK);

            }else{
                System.out.println("资源不存在!!!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return responseEntity;
    }

13.1 过滤器【Filter】与拦截器【Interceptor】区别

  • 过滤器属性web组件,拦截器属于springMVC核心组件。
  • 过滤器执行Servlet之前执行,拦截器执行Servlet【DispatcherServlet】之后且执行Controller【请求处理器】之前【之后】执行。

13.2 实现拦截器两种方式

  • 直接实现HandlerInterceptor接口
  • 直接继承handlerInterceptorAdapter适配器类

13.3 实现拦截器代码

定义拦截器

@Component("myInterceptor")
public class MyInterceptor extends HandlerInterceptorAdapter {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("1. ==>MyInterceptor->preHandle()!!!");
        return true;
    }
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("3. ==>MyInterceptor->postHandle()!!!");
    }
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("4. ==>MyInterceptor->afterCompletion()!!!");
    }
}

装配拦截器【springmvc.xml】

<!--    装配拦截器-->
    <mvc:interceptors>
<!--        为所有Controller装配拦截器-->
<!--        <bean id="myInterceptor" class="com.atguigu.interceptor.MyInterceptor"></bean>-->
        <ref bean="myInterceptor"></ref>
<!--        为局部Controller装配拦截器-->
<!--        <mvc:interceptor >-->
<!--            <mvc:mapping path="/"/>-->
<!--            <mvc:exclude-mapping path="/"/>-->
<!--            <bean class="com"></bean>-->
<!--        </mvc:interceptor>-->
    </mvc:interceptors>

13.4 单个Interceptor工作原理

  • 请求指定Controller的url
  • preHandle():处理请求之前业务,return true时
  • 执行Controller的url
  • postHandle():处理响应之前业务
  • 视图渲染【通过视图解析器解析出视图对象,视图对象将数据存放到域中,并跳转路径】
  • afterCompletion():处理外所有业务之后执行【释放资源等】

13.5 多个Interceptor工作原理

  • 请求指定Controller的url
  • 拦截器1的preHandle():处理请求之前业务,return true时
  • 拦截器2的preHandle():处理请求之前业务,return true时
  • 执行Controller的url
  • 拦截器2postHandle():处理响应之前业务
  • 拦截器1postHandle():处理响应之前业务
  • 视图渲染【通过视图解析器解析出视图对象,视图对象将数据存放到域中,并跳转路径】
  • 拦截器2的afterCompletion():处理外所有业务之后执行【释放资源等】
  • 拦截器1的afterCompletion():处理外所有业务之后执行【释放资源等】
    源码解析
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
 HandlerInterceptor[] interceptors = this.getInterceptors();
 if (!ObjectUtils.isEmpty(interceptors)) {
     for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
         HandlerInterceptor interceptor = interceptors[i];
         if (!interceptor.preHandle(request, response, this.handler)) {
             this.triggerAfterCompletion(request, response, (Exception)null);
             return false;
         }
     }
 }

 return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
     HandlerInterceptor[] interceptors = this.getInterceptors();
     if (!ObjectUtils.isEmpty(interceptors)) {
         for(int i = interceptors.length - 1; i >= 0; --i) {
             HandlerInterceptor interceptor = interceptors[i];
             interceptor.postHandle(request, response, this.handler, mv);
         }
     }

 }

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
     HandlerInterceptor[] interceptors = this.getInterceptors();
     if (!ObjectUtils.isEmpty(interceptors)) {
         for(int i = this.interceptorIndex; i >= 0; --i) {
             HandlerInterceptor interceptor = interceptors[i];

             try {
                 interceptor.afterCompletion(request, response, this.handler, ex);
             } catch (Throwable var8) {
                 logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
             }
         }
     }

 }

13.6 拦截器中preHandle()方法 return false时的工作原理

  • 第一个拦截器的preHandle()方法 return false时的工作原理

    • 只执行第一个拦截器的preHandle(),后续代码均未执行。

    • 源码

//DispatcherServlet 的499行代码
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

非第一个拦截器的preHandle()方法 return false时的工作原理

  • 执行两处代码
    • 执行当前拦截器【preHandler() return false的拦截器】和执行拦截器的preHandle()方法均会执行。
    • 执行之前拦截器的afterCompletion()方法

异常识别

回顾RuntimeException种类

  • NullPointerException null.
  • IndexOutofBoundsException
  • ClassCastException 猫不能转换为狗
  • NumberFormatException 不能将“abc”转换为int
  • ArithMeticException 0不能作为除数

异常处理【】

try-catch-finally throw throws

14.1 SpringMVC中默认异常处理器【DefaultHandlerExceptionResolver】

  • 默认可以处理十几种异常,如:HttpRequestMethodNotSupportedException

  • 异常处理器的返回值类型为:ModelAndView

protected ModelAndView doResolveException(
      HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
   try {
      if (ex instanceof HttpRequestMethodNotSupportedException) {
         return handleHttpRequestMethodNotSupported(
               (HttpRequestMethodNotSupportedException) ex, request, response, handler);
      }

14.2 SpringMVC中的SimpleMappingExceptionResolver

  • 装配SimpleMappingExceptionResolver
<bean id="mappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="java.lang.NullPointerException">nullPointerEx</prop>
<!--                <prop key="java.lang.ArithmeticException">amEx</prop>-->
<!--                <prop key="java.lang.ClassCastException">ccEx</prop>-->
            </props>
        </property>
    </bean>

源码解析

//SimpleMappingExceptionResolver 【无源码,76行】
@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    String viewName = this.determineViewName(ex, request);
    if (viewName != null) {
        Integer statusCode = this.determineStatusCode(request, viewName);
        if (statusCode != null) {
            this.applyStatusCodeIfPossible(request, response, statusCode);
        }

        return this.getModelAndView(viewName, ex, request);
    } else {
        return null;
    }
}
//SimpleMappingExceptionResolver 【无源码,184行】
protected ModelAndView getModelAndView(String viewName, Exception ex) {
        ModelAndView mv = new ModelAndView(viewName);
        if (this.exceptionAttribute != null) {
            mv.addObject(this.exceptionAttribute, ex);
        }

        return mv;
    }
//后续会执行【视图渲染】操作

15.1 扩展三个对象

  • HandlerAdapter【请求处理器适配器】:调用handle()方法,执行请求处理器的目标方法。

  • HandlerExcutionChain【请求处理器执行链】:

    • 作用:
      • Handler execution chain, consisting of handler object and any handler interceptors.
      • 请求处理器执行链对象,包含请求处理器对象和所有请求处理器的拦截器对象。
  • HandlerMapping【请求处理器映射器】

    • 作用
Interface to be implemented by objects that define a mapping between
* requests and handler objects.

请求处理器映射器,将所有的请求和请求处理器对象做关联映射。

15.2 源码解析【有URL有异常】SpringMVC工作原理

//DispatchServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // Determine handler for the current request.
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // Actually invoke the handler.
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         applyDefaultViewName(processedRequest, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
       //视图渲染入口
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}

16.1 SSM整合思路

  • Spring+Mybatis

  • Spring+SpringMVC

16.2 SSM整合步骤

  • 导入jar包

    • 低配20个jar包
    • 高配无上限
  • 编写配置文件

    • log4j.xml
    • mybatis-config.xml
      • 配置数据库环境【environments】
        • 连接池
        • 事务管理器
      • 配置映射文件加载路径【mappers】
      • settings
      • typeAliases
      • plugins
    • XXXMapper.xml
    • spring.xml 【applicationContext.xml|beans.xml】
      • 配置数据库环境
        • 连接池
        • 事务管理器
      • 装配组件扫描器
        • 【除了springmvc层之外】
    • springmvc.xml
      • 装配组件扫描器【springmvc层】
      • 装配视图解析器
      • 转配默认请求处理器及后续问题
    • web.xml
  • 解决框架冲突问题

    • spring整合mybatis时,两者均管理数据库连接池&事务。【冲突】

      • 解决方案:交给spring管理,mybatis中的相应代码删除。

      • mybatis中的核心对象管理,也应该交个spring管理

        • SqlSession->SqlSessionFactory
<!--    使用SqlSessionFactoryBean管理SqlSessionFactory对象-->
    <bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--        装配mybatis-config.xml核心配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!--        装配dataSource数据源-->
        <property name="dataSource" ref="dataSource"></property>
<!--            装配别名处理器-->
        <property name="typeAliasesPackage" value="com.atguigu.pojo"></property>
<!--        装配映射文件加载路径-->
        <property name="mapperLocations" value="classpath:com/atguigu/mapper/*.xml"></property>
    </bean>

xxxMapper

<!--    MapperScannerConfigurer管理XXXMapper对象【为生成&管理代理对象】-->
<!--    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">-->
<!--        <property name="basePackage" value="com.atguigu.mapper"></property>-->
<!--    </bean>-->
    <mybatis-spring:scan base-package="com.atguigu.mapper"></mybatis-spring:scan>

spring整合springmvc时

  • 组件扫描冲突【springmvc装配Controller层组件,spring装配除了Controller层组件】

    • 以为不解决冲突,发现事务无法正常使用
<!-- spring.xml   组件扫描器-->
    <context:component-scan base-package="com.atguigu">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

<!-- Springmvc.xml   装配组件扫描器:cotroller层-->
    <context:component-scan base-package="com.atguigu" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

两个容器对象,应该先加载spring容器对象【springMvc容器对象在DispatcherServlet中管理】

  • 使用Listener监听器管理Spring容器对象
web.xml中注册监听器
<!--    注册监听器【ContextLoaderListener】,创建&管理spring核心容器对象-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

SSM框架(Spring + Spring MVC + MyBatis)

SSM框架(Spring + Spring MVC + MyBatis)相关教程

  1. 教你十分钟快速搭建springBoot项目实战

    教你十分钟快速搭建springBoot项目实战 为什么80%的码农都做不了架构师? 首先申明,本文并没有原理性的东西(请自行百度),适合于刚接触springBoot并有一定javaweb开发基础的人群,大神和小白请出门左拐。 ##使用工具Eclipse、Maven、springBoot本文不会讲解M

  2. SpringBoot开发案例之整合Dubbo提供者(一)

    SpringBoot开发案例之整合Dubbo提供者(一) 为什么80%的码农都做不了架构师? 既然是开发案例,显然不会扯那么多老婆舌,有不清楚这两个东东的请自行百度。 JDK1.7、Maven、Eclipse、SpringBoot1.5.1、Dubbo2.8.4 ##项目结构 ##相关配置pom.xml: project xmln

  3. SpringBoot开发案例之整合Dubbo提供者(二)

    SpringBoot开发案例之整合Dubbo提供者(二) 为什么80%的码农都做不了架构师? 大家有没有注意到,上一篇中提供者,暴露接口的方式?混搭。springboot本身接口实现使用了注解的方式,而Dubbo暴露接口使用的是配置文件的实现方式,即如下: import org.springfra

  4. Spring使用Set注入和拓展注入

    Spring使用Set注入和拓展注入 Spring中有三种方式注入,第一种是之前文章所写的构造器注入,第二种是使用Set注入,第三种是拓展注入(利用标签) 使用Set注入: 使用的pojo为: 并生成get和set方法 XML配置: bean id=address class=com.mi.pojo.Address prop

  5. Spring 基于注解的AOP

    Spring 基于注解的AOP AOP 基于注解的使用方法 1. pom.xml 2. 开启注解AOP 3. 添加Pointcut和Around 4. 设置自定义标签扫描路径 5. 创建service类 6. 测试类 7. 查看打印 8. 断点看userService对象 9. spring对AOP注解的支撑 基于注解的使用方法 dependencies

  6. SpringMVC简单配置

    SpringMVC简单配置 基本步骤 1.新建一个普通Maven项目 2.添加Web Module依赖 右键项目-Add FrameWork Support-Web 3.添加依赖pom.xml ?xml version=1.0 encoding=UTF-8?project xmlns=http://maven.apache.org/POM/4.0.0 xmlns:xsi=http://www.w3.org/2001/XM

  7. Spring Boot中出现java.nio.charset.MalformedInputException: I

    Spring Boot中出现java.nio.charset.MalformedInputException: Input length = 1绑定失败 项目场景: IDEA使用Spring Boot整合tk mybatis出现的问题 问题描述: java.nio.charset.MalformedInputException: Input length = 1 绑定失败 原因分析: 原因1: int

  8. springboot2.0整合jsp

    springboot2.0整合jsp 文章目录 前提条件 整合 1.导入相关包 2.配置application.properties 3.实现 controller user App启动类 jsp页面 注意 运行结果 前提条件 知道并且熟悉 maven 技术 学习过jsp页面视图技术 了解熟悉spring mvc技术 整合 因为springboot官