Spring


Spring 介绍


  • 官网:https://spring.io
  • Spring Framework:Spring框架,是Spring中最早最核心的技术,也是所有其他技术的基础
  • SpringBoot:Spring是来简化开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发
  • SpringCloud:这个是用来做分布式之微服务架构的相关开发
  • 还有:SpringData 、SpringSecurity 等等

Spring 的发展史:

  • 随着时间推移,版本不断更新维护,目前最新的是Spring5
  • Spring1.0 是 纯配置文件 开发
  • Spring2.0 为了简化开发引入了 注解 开发,此时是配置文件加注解的开发方式
  • Spring3.0 已经可以进行 纯注解 开发,使开发效率大幅提升
  • Spring4.0 根据 JDK的版本升级 对 个别API 进行了调整
  • Spring5.0 已经 全面支持JDK8,现在Spring最新的是5系列所以建议把JDK安装成1.8版

Spring 系统架构


Spring Framework 是Spring生态圈中最基础的项目,是其他项目的根基

Spring各个版本架构图

因为 Spring Framework 的 5 版本目前没有最新的架构图,主要研究的是 4 的架构图

Spring Framework 4.X 版本

  • Core container(核心层)
    • 核心容器,这个模块是 Spring 最核心的模块,其他的都需要依赖该模块
  • AOP层
    • AOP:面向切面编程,它依赖核心层容器,目的是 在不改变原有代码的前提下对其进行功能增强
    • Aspects:AOP是思想,Aspects是对AOP思想的具体实现
  • 数据层
    • Data Access:数据访问,Spring全家桶 中有对数据访问的具体实现技术
    • Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如:Mybatis
    • Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现
  • Web层
    • 主要指 SpringMVC框架
  • Test层
    • Spring主要整合了 Junit 来完成 单元测试 和 集成测试

IoC(Inversion of Control)


IoC(Inversion of Control):控制反转,使用对象时,由主动 new 产生对象转换为由 外部 提供对象,此过程中 对象创建控制权由 程序 转移到 外部,此思想称为控制反转

  • 控制反转 的含义:
    • 业务层要用数据层的类对象,以前是自己 new 的
    • 现在自己不 new 了,交给别人 [外部] 来创建对象
    • 别人 [外部] 就反转控制了数据层对象的创建权
    • IOC思想中的别人 [外部] 指的就是 Spring 的 IOC容器

Spring技术 对 IoC思想 进行了实现:

  • Spring提供了一个容器,称为 IoC容器,用来充当IoC思想中的"外部",IoC容器 是 Spring创建了一个容器用来存放所创建的对象

    IoC容器的实现

  • IoC容器 负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象,被创建或被管理的对象在IoC容器中统称为 Bean

    • IoC容器 中放的就是一个个的 Bean对象,也就是说 IoC容器 管理的对象叫 Bean 对象

IoC 入门案例分析(XML版本)

  1. Spring是使用容器来管理bean对象的,主要管理项目中所使用到的类对象,比如:(Service和Dao)
  2. 将被管理的对象告知IoC容器的方式:使用配置文件
  3. 被管理的对象交给IoC容器,要想从容器中获取对象,通过 Spring框架提供相应的接口 获取到IoC文件
  4. IoC容器得到后,通过 调用Spring框架提供对应接口中的方法 从容器中获取bean
  5. 使用Spring导入坐标:用别人的东西,就需要 在 pom.xml 添加对应的依赖

流程:

  1. 添加Spring的依赖 jar 包,pom.xml,这步就是导入 Spring 坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>
    </dependencies>
  2. 创建 BookService、BookServiceImpl 、BookDao 和 BookDaoImpl 四个类

  3. 添加 Spring配置文件 applicationContext.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?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">
    <!--1.导入spring的坐标spring-context,对应版本是5.2.10.RELEASE-->

    <!--2.配置bean-->
    <!--bean标签标示配置bean
    id属性标示给bean起名字
    class属性表示给bean定义类型-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

    </beans>
    • bean定义时 id 属性在 同一个上下文中 (配置文件)不能重复
  4. 获取 IOC 容器 和 获取 Bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class App {
    public static void main(String[] args) {
    // 3.获取IoC容器
    // ApplicationContext 要与配置文件名相同,并且这个一个接口
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

    // 4.获取bean(根据bean配置id获取)
    // BookDao bookDao = (BookDao) ctx.getBean("bookDao");
    // bookDao.save();

    BookService bookService = (BookService) ctx.getBean("bookService");
    bookService.save();

    }
    }

DI(Dependency Injection)


DI(Dependency Injection):依赖注入,在容器中建立 bean 与 bean 之间的依赖关系的整个过程

  • 依赖注入 的含义:
    • 业务层要用数据层的类对象,以前是自己 new 的
    • 现在自己不 new 了,靠别人 [外部其实指的就是IOC容器] 来给注入进来

IoC 和 DI 的目标:充分解耦

  • 使用IoC容器管理 bean(IOC)
  • 在IoC容器内将 有依赖关系的bean 进行关系绑定(DI)
  • 最终结果为:使用对象时 不仅可以直接从 IoC容器 中获取,并且获取到的 bean 已经绑定了所有的依赖关系

DI 入门案例分析(XML版本)

  1. 基于 IoC 管理 bean
  2. 不再保留 Service 中使用 new 形式创建的 Dao 对象
  3. 提供一个方法使得 Service 中需要的 Dao 对象传入 Service 中
  4. 通过 配置 来建立 Service 与 Dao 之间的关系

流程:

  1. 删除代码中的 new

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class BookServiceImpl implements BookService {
    // 删除业务层中使用new的方式创建的dao对象
    // private BookDao bookDao = new BookDaoImpl();
    private BookDao bookDao;

    public void save() {
    System.out.println("book service save ...");
    bookDao.save();
    }
    }
  2. 提供依赖对象对应的 setter 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class BookServiceImpl implements BookService {

    //删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
    public void save() {
    System.out.println("book service save ...");
    bookDao.save();
    }

    //提供对应的set方法
    public void setBookDao(BookDao bookDao) {
    this.bookDao = bookDao;
    }
    }
  3. 配置 service 与 dao 之间的关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?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">
    <!--1.导入spring的坐标spring-context,对应版本是5.2.10.RELEASE-->

    <!--2.配置bean-->
    <!--bean标签标示配置bean
    id属性标示给bean起名字
    class属性表示给bean定义类型-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <!--7.配置service与dao的关系-->
    <!--property标签表示配置当前bean的属性
    name属性表示配置哪一个具体的属性
    ref属性表示参照哪一个bean-->
    <property name="bookDao" ref="bookDao"/>
    </bean>

    </beans>

    上述指代不同

    • name=“bookDao” 中 bookDao 的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前

      面加 set 找对应的 setBookDao() 方法进行对象注入

    • ref=“bookDao” 中 bookDao 的作用是让 Spring 能在IOC容器中找到 id 为 bookDao 的Bean对象给

      bookService 进行注入


依赖注入 的两种方式


setter 注入

  • 简单类型(基本数据类型 与 String)
  • 引用类型

构造器 注入

  • 简单类型(基本数据类型 与 String)
  • 引用类型

setter 注入

  1. 在 bean 中定义 引用类型属性,并提供可访问的 set 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class BookServiceImpl implements BookService {

    // 注入引用类型
    private BookDao bookDao;

    // 注入简单类型(String)
    private String databaseName;

    // 注入简单类型(基本数据类型)
    private int connectionNum;

    public void setBookDao(BookDao bookDao) {
    this.bookDao = bookDao;
    }

    public void setConnectionNum(int connectionNum) {
    this.connectionNum = connectionNum;
    }

    public void setDatabaseName(String databaseName) {
    this.databaseName = databaseName;
    }
    }
  2. 配置中使用 property 标签 ref 属性注入 引用类型对象

    1
    2
    3
    4
    5
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
    </bean>

    <bean id="bookDao" class="com.itheima.dao.imipl.BookDaoImpl"/>
  3. 如果是注入简单数据类型

    1
    2
    3
    4
    5
    6
    7
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
    <property name="databaseName" value="mysql"/>
    <property name="connectionNum" value="10"/>
    </bean>

    <bean id="bookDao" class="com.itheima.dao.imipl.BookDaoImpl"/>

总结:

  • 对于 引用数据类型 使用的是 <property name="" ref=""/>
  • 对于 简单数据类型 使用的是 <property name="" value=""/>

构造器 注入

构造器注入 就是 构造方法注入,如果是简单数据类型,同理

流程:

  1. 项目中添加BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService 和 BookServiceImpl 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;

    public BookServiceImpl(BookDao bookDao, UserDao userDao) {
    this.bookDao = bookDao;
    this.userDao = userDao;
    }

    public void save() {
    System.out.println("book service save ...");
    bookDao.save();
    userDao.save();
    }
    }
  2. resources 下提供 spring 的配置文件

    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    <?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">
    <!--
    标准书写
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    根据构造方法参数名称注入
    <constructor-arg name="connectionNum" value="10"/>
    <constructor-arg name="databaseName" value="mysql"/>
    </bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <constructor-arg name="userDao" ref="userDao"/>
    <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>
    -->
    <!--
    解决形参名称的问题,与形参名不耦合

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    根据 构造方法 参数类型 注入
    <constructor-arg type="int" value="10"/>
    <constructor-arg type="java.lang.String" value="mysql"/>
    </bean>

    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <constructor-arg name="userDao" ref="userDao"/>
    <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>-->

    <!--解决参数类型重复问题,使用 位置 解决参数匹配-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <!--根据构造方法参数位置注入-->
    <!-- index 表明参数的位置 -->
    <constructor-arg index="0" value="mysql"/>
    <constructor-arg index="1" value="100"/>
    </bean>

    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <constructor-arg name="userDao" ref="userDao"/>
    <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>

    </beans>

  3. 编写 AppForDIConstructor 运行类,加载 Spring 的 IoC 容器,并从中获取对应的 bean 对象

    1
    2
    3
    4
    5
    6
    7
    public class AppForDIConstructor {
    public static void main( String[] args ) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    BookService bookService = (BookService) ctx.getBean("bookService");
    bookService.save();
    }
    }

总结:

  • 简单数据类型

    1
    2
    3
    <bean ...>
    <constructor-arg name="" index="" type="" value=""/>
    </bean>
  • 引用数据类型

    1
    2
    3
    <bean ...>
    <constructor-arg name="" index="" type="" ref=""/>
    </bean>

依赖注入的方式选择上:

  • 建议使用 setter 注入
  • 第三方技术根据情况选择

依赖 自动装配

IoC容器 根据 bean 所依赖的资源 在容器中自动查找 并注入到 bean 中的过程称为 自动装配

自动装配方式:

  • 按 类型(常用)
  • 按 名称
  • 按 构造方法
  • 不启用自动装配

流程:

自动装配 需要修改 applicationContext.xml 配置文件:

  1. 将 <property> 标签删除
  2. 在 <bean> 标签中添加 autowire 属性

eg:

1
2
3
4
5
6
7
8
9
10
<?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">

<bean class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>

</beans>

注意:

  1. 需要 注入属性的类中对应属性 的 setter 方法不能省略

  2. 被注入的对象必须要被 Spring 的 IOC容器 管理

  3. 按照类型在 Spring 的 IOC容器 中 如果找到 多个对象,会报 NoUniqueBeanDefinitionException

  4. 自动装配 用于 引用类型依赖 注入,不能对 简单类型 进行操作

  5. 使用按 类型 装配时(byType)必须保障容器中相同类型的 bean 唯一,推荐使用

  6. 使用按 名称 装配时(byName)必须保障 容器 中具有 指定名称 的 bean,因变量名与配置耦合,不推荐使用

    1
    2
    3
    <bean id = "bookDao" class = "com.itheima.dao.impl.BaoDaoImpl">

    <bean id = "bookService" class = "com.itheima.service.impl.BookServiceImpl" autowire = "byName">
  7. 自动装配优先级 低于 setter 注入 与 构造器注入,同时出现时自动装配配置失效


集合 注入


常见的集合类型:

  • 数组
  • List
  • Set
  • Map
  • Properties

流程:

在 bookDao 的 bean 标签中 使用进行注入

1
2
3
4
5
6
7
8
9
10
<!-- 数组注入 -->
<bean id = "bookDao" class = "com.itheima.dao.impl.BookDaoImpl">
<property name = "array"> <!-- 这里的name是变量名 -->
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
</bean>
1
2
3
4
5
6
7
8
9
10
<!-- List注入 -->
<bean id = "bookDao" class = "com.itheima.dao.impl.BookDaoImpl">
<property name = "list">
<list>
<value>100</value>
<value>200</value>
<value>300</value>
</list>
</property>
</bean>
1
2
3
4
5
6
7
8
9
10
<!-- set注入 -->
<bean id = "bookDao" class = "com.itheima.dao.impl.BookDaoImpl">
<property name = "set">
<set>
<value>100</value>
<value>200</value>
<value>300</value>
</set>
</property>
</bean>
1
2
3
4
5
6
7
8
9
10
<!-- map注入 -->
<bean id = "bookDao" class = "com.itheima.dao.impl.BookDaoImpl">
<property name = "map">
<map>
<entry key = "hello1" value = "1"/>
<entry key = "hello2" value = "2"/>
<entry key = "hello3" value = "3"/>
</map>
</property>
</bean>
1
2
3
4
5
6
7
8
9
10
<!-- properties注入 -->
<bean id = "bookDao" class = "com.itheima.dao.impl.BookDaoImpl">
<property name = "properties">
<props>
<prop key = "hello1">1</prop>
<prop key = "hello2">2</prop>
<prop key = "hello3">3</prop>
</props>
</property>
</bean>

第三方 依赖注入


流程:

  1. pom.xml 添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
    </dependency>
  2. 在 applicationContext.xml 配置文件中添加DruidDataSource的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?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">

    <!--管理DruidDataSource对象-->
    <bean class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    </bean>
    </beans>
    • driverClassName:数据库驱动
    • url:数据库连接地址
    • username:数据库连接用户名
    • password:数据库连接密码
    • 数据库连接的 四要素 要和 自己使用的数据库 信息一致
    • 注入数据库连接四要素 的方式:setter 注入
  3. 从 IoC容器 中获取对应的 bean 对象

    1
    2
    3
    4
    5
    6
    7
    public class App {
    public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    DataSource dataSource = (DataSource) ctx.getBean("dataSource");
    System.out.println(dataSource);
    }
    }

bean


bean 的基础配置

bean的基础配置

  • 重点:bean 标签的 id 和 class 属性的使用

bean 的别名配置

bean的name属性

  • 获取 bean 无论是通过 id 还是 name 获取,如果无法获取到,将抛出异常 NoSuchBeanDefinitionException

bean 作用范围 scope 配置

bean的scope配置

  • 主要就是控制 bean 创建的数量,是一个还是多个,bean 默认是 singleton(单例模式)
  • 适合交给 IoC 容器进行管理的 bean:
    • 表现层对象
    • 业务层对象
    • 数据层对象
    • 工具对象
  • 不适合交给 IoC 容器进行管理的 bean:
    • 封装实体的域对象

bean 的实例化

bean 本质上就是对象,对象在 new 的时候会使用构造方法完成,那创建 bean 也是使用构造方法完成的

方式1:使用构造方法(常用)

  1. 提供可以访问的 构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class BookDaoImpl implements BookDao {

    public BookDaoImpl() {
    System.out.println("book constructor is running ...");
    }

    public void save() {
    System.out.println("book dao save ...");
    }
    }
  2. 配置

    1
    <bean id = "bookDao" class = "com.itheima.dao.impl.bookDaoImpl"/>
    • 注意:无参构造方法如果不存在,会抛出异常 BeanCreationException

方式2:使用静态工厂

  1. 准备一个 OrderDao 和 OrderDaoImpl 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface OrderDao {
    public void save();
    }

    public class OrderDaoImpl implements OrderDao {
    public void save() {
    System.out.println("order dao save ...");
    }
    }
  2. 创建一个工厂类 OrderDaoFactory 并提供一个 静态方法

    1
    2
    3
    4
    5
    6
    //静态工厂创建对象
    public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
    return new OrderDaoImpl();
    }
    }
  3. 编写 AppForInstanceOrder 运行类,在类中通过工厂获取对象

    1
    2
    3
    4
    5
    6
    7
    public class AppForInstanceOrder {
    public static void main(String[] args) {
    //通过静态工厂创建对象
    OrderDao orderDao = OrderDaoFactory.getOrderDao();
    orderDao.save();
    }
    }
  4. 配置

    1
    2
    <bean id = "orderDao" class = "com.itheima.factory.OrderDaoFactory" 
    factory-method = "getOrderDao"/>
    • factory-method:具体工厂类中创建对象的方法名
  5. 在 AppForInstanceOrder 运行类,使用从 IoC容器 中获取 bean的方法 进行运行测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class App {
    public static void main(String[] args) {

    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

    OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
    orderDao.save();

    }
    }

方式3:使用实例工厂

  1. 准备一个 UserDao 和 UserDaoImpl 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface UserDao {
    public void save();
    }

    public class UserDaoImpl implements UserDao {
    public void save() {
    System.out.println("user dao save ...");
    }
    }
  2. 创建一个工厂类 OrderDaoFactory 并 提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法

    1
    2
    3
    4
    5
    public class UserDaoFactory {
    public UserDao getUserDao(){
    return new UserDaoImpl();
    }
    }
  3. 编写 AppForInstanceUser 运行类,在类中通过工厂获取对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class AppForInstanceUser {
    public static void main(String[] args) {
    //创建实例工厂对象
    UserDaoFactory userDaoFactory = new UserDaoFactory();

    //通过实例工厂对象创建对象
    UserDao userDao = userDaoFactory.getUserDao();
    userDao.save();
    }
  4. 在spring的配置文件中 添加配置

    1
    2
    3
    4
    5
    <!-- 先造出来工厂对象 -->
    <bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>

    <!-- 使用工厂对象 -->
    <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

    说明


方式4:使用FactoryBean

流程:

  1. 创建一个 UserDaoFactoryBean 的类,实现 FactoryBean 接口,重写接口的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class UserDaoFactoryBean implements FactoryBean<UserDao> {

    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
    return new UserDaoImpl();
    }

    //返回所创建类的Class对象
    public Class<?> getObjectType() {
    return UserDao.class;
    }
    }
  2. 在Spring的配置文件中进行 配置

    1
    <bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

FactoryBean接口其实会有三个方法,分别是:

1
2
3
4
5
6
7
T getObject() throws Exception;

Class<?> getObjectType();

default boolean isSingleton() {
return true;
}
  • 方法一:getObject(),被重写后,在方法中进行 对象的创建 并 返回
  • 方法二::getObjectType(),被重写后,主要返回的是 被创建类的Class对象
  • 方法三:从容器中 获取该对象的多个值,打印到控制台,查看是否为同一个对象
    • 默认是 单例,如果要修改,将 isSingleton() 方法进行重写,修改返回为 false

bean 的生命周期

指的是:bean对象 从创建到销毁 的整体过程

bean 生命周期控制:指的是在 bean 创建后到销毁前 做一些事情

生命周期的设置:

  • bean 创建之后,想要添加内容,比如:用来初始化需要用到资源
  • bean 销毁之前,想要添加内容,比如:用来释放用到的资源

流程:

  1. 添加初始化和销毁方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class BookDaoImpl implements BookDao {
    public void save() {
    System.out.println("book dao save ...");
    }

    //表示bean初始化对应的操作
    public void init() {
    System.out.println("init...");
    }

    //表示bean销毁前对应的操作
    public void destroy() {
    System.out.println("destory...");
    }

    }
  2. 配置 生命周期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?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">

    <!--init-method:设置bean初始化生命周期回调函数-->
    <!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
    </bean>

    </beans>

从结果中可以看出,init 方法执行了,但是 destroy 方法却未执行,原因是:

  1. Spring 的 IoC 容器是运行在 JVM 中
  2. 运行 main 方法后,JVM 启动,Spring加载配置文件生成IoC容器,从容器获取 bean 对象,然后调方法执行
  3. main 方法执行完后,JVM退出,这个时候 IoC 容器中的 bean 还没有来得及销毁就已经结束了

因为 ApplicationContext 中没有 close 方法,需要将 ApplicationContext 更换成 ClassPathXmlApplicationContext,运行程序,就能执行 destroy 方法的内容

1
2
3
4
5
6
7
8
9
10
11
12
public class AppForLifeCycle {
public static void main( String[] args ) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
// 注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
// ctx.registerShutdownHook();
// 关闭容器,close方法比较暴力
ctx.close();
}
}
  • 在容器未关闭之前,提前设置好回调函数,让 JVM 在退出之前回调此函数来关闭容器,调用ctx的 registerShutdownHook() 方法

    1
    ctx.registerShutdownHook();

close() 和 registerShutdownHook() 的异同:

  • 相同点:这两种都能用来关闭容器
  • 不同点:close() 是在 调用的时候 关闭,registerShutdownHook() 是在 JVM退出前 调用关闭

上面的流程需要手动添加 初始化 和 销毁方法,即需要编码也需要配置,十分繁琐,Spring提供了两个接口来完成生命周期的控制,好处是可以不用再进行配置 init-method 和 destroy-method

修改BookServiceImpl类,添加两个接口 InitializingBean, DisposableBean 并实现接口中的两个方法 afterPropertiesSet 和 destroy

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BookDaoImpl implements BookService, InitializingBean, DisposableBean {
public void save() {
System.out.println("book dao save ...");
}

public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}

public void destroy() throws Exception {
System.out.println("service destroy");
}
}
  • 对于 InitializingBean 接口中的 afterPropertiesSet 方法,翻译过来为属性设置之后
  • 对于 BookServiceImpl 来说,bookDao 是它的一个属性
  • setBookDao 方法是 Spring 的 IOC容器 为其 注入属性 的方法

对于 bean的生命周期 控制在 bean 的整个生命周期 中所处的位置如下:

  • 初始化容器

    1. 创建对象(内存分配)

    2. 执行构造方法

    3. 执行属性注入(set操作)

    4. 执行 bean 初始化方法

  • 使用bean

    1. 执行业务操作
  • 关闭/销毁容器

    1. 执行 bean 销毁方法

关闭容器 的 两种方式:

在 ConfigurableApplicationContext 是 ApplicationContext 的子类

  • 手动关闭容器的方法:close() 方法
  • 注册关闭钩子,在虚拟机退出前先关闭容器再关闭虚拟机:registerShutdownHook() 方法

Spring 中加载 properties 文件


流程:

  1. 准备 .properties 配置文件

    1
    2
    3
    4
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
    jdbc.username=root
    jdbc.password=root
  2. 开启 context 命名空间,在 applicationContext.xml 中开 context 命名空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    </beans>
  3. 加载 properties 配置文件

    • 在配置文件中使用 context 命名空间下的 标签 来加载 properties 配置文件
    1
    <context:property-placeholder location="jdbc.properties"/>
  4. 完成属性注入

    • 使用${key}来读取 properties 配置文件中的内容并完成属性注入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </bean>
    </beans>

上面流程的优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--方式一 -->
<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>

<!--方式二-->
<context:property-placeholder location="*.properties" system-properties-mode="NEVER"/>

<!--方式三 -->
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>

<!--方式四-->
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
</beans>

system-properties-mode:设置为 NEVER ,表示 不加载系统属性

说明:

  • 方式一:可以实现,如果配置文件多的话,每个都需要配置
  • 方式二:*.properties 代表所有以 properties 结尾的文件都会被加载,可以解决方式一的问题,但是不标准
  • 方式三:标准的写法,classpath: 代表的是从 根路径下 开始查找,但是只能查询当前项目的根路径
  • 方式四:不仅可以加载 当前项目 还可以加载 当前项目所依赖的所有项目 的根路径下的 properties 配置文件

总结:

  • 开启context命名空间的方法:

    context命名空间

  • 加载 properties 配置文件的方法:

    1
    <context:property-placeholder location="" system-properties-mode="NEVER"/>
  • 在 applicationContext.xml 引入 properties 配置文件中的值的方法:

    1
    ${key}

容器


容器的创建方式


方式1:加载类路径下的 XML配置文件

1
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

方式2:加载文件系统下的 XML配置文件

1
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\...\\applicationContext.xml"); 

方式3:加载多个配置文件

1
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");

Bean 的 三种获取方式


方式1:

1
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
  • 存在的问题是:每次获取的时候 都需要进行 类型转换

方式2:

1
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
  • 这种方式可以解决类型强转问题,但是参数又多加了一个,相对来说没有简化多少

方式3:

1
BookDao bookDao = ctx.getBean(BookDao.class);
  • 这种方式类似于 依赖注入中 的 按类型注入,但是必须要确保 IOC容器 中该类型对应的 bean 对象只能有一个

BeanFactory


BeanFactory 是 ApplicationContext 的 顶层接口

BeanFactory的关系层次

流程:

使用 BeanFactory 来创建 IOC容器 的具体实现方式为:

1
2
3
4
5
6
7
8
public class AppForBeanFactory {
public static void main(String[] args) {
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean(BookDao.class);
bookDao.save();
}
}

BeanFactory 与 ApplicationContext 的区别:

  • BeanFactory 是 延迟加载 ,只有在获取 bean对象 的时候才会去创建

  • ApplicationContext 是 立即加载 ,容器加载的时候就会创建bean对象

    • ApplicationContext 要想 成为延迟加载,只需要按照如下方式进行配置:

      1
      2
      3
      4
      5
      6
      7
      8
      <?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">

      <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" lazy-init="true"/>
      </beans>

核心容器 相关总结


容器相关:

  • BeanFactory是 IoC 容器的 顶层接口,初始化BeanFactory对象时,加载的bean 延迟加载
  • ApplicationContext 接口是 Spring容器 的 核心接口,初始化时bean 立即加载
  • ApplicationContext 接口提供 基础的bean操作 相关方法,通过其他接口扩展其功能
  • ApplicationContext接口 常用初始化类
    • ClassPathXmlApplicationContext(常用)
    • FileSystemXmlApplicationContext

bean相关:

bean相关

  • 最常用的:id 和 class

依赖注入相关:

依赖注入相关


注解开发


Spring对注解支持的版本历程:

  • 2.0版 开始支持注解
  • 2.5版 注解功能趋于完善
  • 3.0版 支持 纯注解开发

注解开发定义bean


流程:

  1. 删除原XML配置

    • 将配置文件中的 <bean> 标签删除掉
    1
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
  2. Dao上添加注解

    • 在BookDaoImpl类上添加 @Component 注解
    1
    2
    3
    4
    5
    6
    @Component("bookDao")
    public class BookDaoImpl implements BookDao {
    public void save() {
    System.out.println("book dao save ..." );
    }
    }

    XML与注解配置 对应关系

  3. 配置 Spring 的注解包扫描

    1
    2
    3
    4
    5
    6
    7
    <?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">
    <context:component-scan base-package="com.itheima"/>
    </beans>

    说明:

    • component-scan

      • component:组件,Spring将管理的 bean 视作自己的一个组件

      • scan:扫描

    • base-package 指定 Spring 框架扫描的包路径,它会扫描指定包及其子包中的所有类上的注解。

      • 包路径越多(如:com.itheima.dao.impl),扫描的范围越小速度越快

      • 包路径越少(如:com.itheima),扫描的范围越大速度越慢

      • 一般扫描到项目的组织名称即Maven的 groupId 下(如:com.itheima)即可

  4. Service 上添加注解

    • 在 BookServiceImpl 类上也添加 @Component 交给Spring框架管理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Component
    public class BookServiceImpl implements BookService {
    private BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
    this.bookDao = bookDao;
    }

    public void save() {
    System.out.println("book service save ...");
    bookDao.save();
    }
    }
    • @Component注解如果不起名称,会有一个默认值就是当前类名首字母小写

对于 @Component 注解,还衍生出了其他三个注解@Controller、@Service、@Repository

这三个注解和 @Component 注解的作用是一样的,分别是属于表现层、业务层、数据层的类


纯注解开发 模式


实现思路:将配置文件 applicationContext.xml 删除掉,使用类来替换

流程:

  1. 创建一个配置类SpringConfig

    1
    2
    public class SpringConfig {
    }
  2. 标识该类为 配置类

    • 在配置类上添加@Configuration注解,将其标识为一个配置类,替换applicationContext.xml
    1
    2
    3
    @Configuration
    public class SpringConfig {
    }
  3. 用 注解替换包 扫描配置

    • 在配置类上添加包扫描注解 @ComponentScan 替换 <context:component-scan base-package=""/>
    1
    2
    3
    4
    @Configuration
    @ComponentScan("com.itheima")
    public class SpringConfig {
    }
  4. 创建 运行类 并执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class AppForAnnotation {

    public static void main(String[] args) {
    // 这里是加载配置类
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

    BookDao bookDao = (BookDao) ctx.getBean("bookDao");
    System.out.println(bookDao);

    BookService bookService = ctx.getBean(BookService.class);
    System.out.println(bookService);
    }
    }

注意:

  1. @Configuration 注解用于设定当前类为配置类

  2. @ComponentScan 注解用于设定扫描路径,此注解只能添加一次,多个数据请用 数组 格式

    1
    @ComponentScan({"com.itheima.service","com.itheima.dao"})
  3. 读取Spring核心配置文件 初始化容器对象 切换为读取 Java配置类初始化容器对象

    1
    2
    3
    4
    5
    //加载配置文件初始化容器
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

    //加载配置类初始化容器
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

bean 的 作用范围


想将 BookDaoImpl 变成 非单例,只需要在其类上添加 @scope 注解

1
2
3
4
5
6
7
8
9
10
@Repository
// @Scope设置bean的作用范围,默认值 singleton(单例)
// prototype:表示非单例
@Scope("prototype")
public class BookDaoImpl implements BookDao {

public void save() {
System.out.println("book dao save ...");
}
}
名称 @Scope
类型 类注解
位置 类定义上方
作用 设置该类创建对象的作用范围 可用于设置创建出的bean是否为单例对象
属性 value(默认):定义bean作用范围, 默认值singleton(单例),可选值prototype(非单例)

bean 的 生命周期


名称 @PostConstruct
类型 方法注解
位置 方法上
作用 设置该方法为初始化方法
属性 无
名称 @PreDestroy
类型 方法注解
位置 方法上
作用 设置该方法为销毁方法
属性 无

对方法进行标识,判断 初始化方法 还是 销毁方法:需要在对应的方法上添加 @PostConstruct 和 @PreDestroy 注解即可,destroy只有在 容器关闭 的时候,才会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
@PostConstruct //在构造方法之后执行,替换 init-method
public void init() {
System.out.println("init ...");
}
@PreDestroy //在销毁方法之前执行,替换 destroy-method
public void destroy() {
System.out.println("destroy ...");
}
}

@PostConstruct 和 @PreDestroy 注解如果找不到,需要导入下面的 jar 包

1
2
3
4
5
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>

总结:

小结


注解开发 依赖注入


注解实现 引用类型 注入


Spring为了使用注解简化开发,并没有提供构造函数注入、setter注入对应的注解,只提供了自动装配的注解实现 @Autowired

名称 @Autowired
类型 属性注解 或 方法注解(了解) 或 方法形参注解(了解)
位置 属性定义上方 或 标准set方法上方 或 类set方法上方 或 方法形参前面
作用 为 引用类型 属性设置值
属性 required:true/false,定义该属性是否允许为null

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;

// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}

说明:

  • @Autowired可以写在 属性 上,也可也写在 setter 方法上,最简单的处理方式是写在属性上并将setter方法删除掉
  • setter 方法 删除 的原因:
    • 自动装配 基于反射设计 创建对象 并通过 暴力反射 为 私有属性进行设值
    • 普通反射 只能获取 public 修饰的内容
    • 暴力反射 除了获取 public 修饰的内容还可以获取 private 修改的内容
    • 所以此处无需提供setter方法
  • 自动装备建议使用无参构造方法创建对象(默认),如果不提供对应的构造方法,需要提供唯一的构造方法

如果 @Autowired 是按照类型注入的,给 BookDao 的两个实现起了名称,它还是有两个bean对象,为什么没有报错?

答:@Autowired默认按照类型自动装配,如果IOC容器中同类的Bean找到多个,就按照变量名和Bean的名称匹配。因为变量名叫bookDao而容器中也有一个booDao,所以可以成功注入。

如果当根据类型在容器中找到多个bean,注入参数的属性名又和容器中 bean 的名称不一致,这个时候该如何解决,就需要使用到@Qualifier来指定注入哪个名称的bean对象

名称 @Qualifier
类型 属性注解 或 方法注解(了解)
位置 属性定义上方 或 标准set方法上方 或 类set方法上方
作用 为引用类型属性指定注入的beanId
属性 value(默认):设置注入的beanId

eg:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao1")
private BookDao bookDao;

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
  • @Qualifier 注解后的值就是需要注入的 bean 的名称
  • @Qualifier 不能独立使用,必须和 @Autowired 一起使用

注解实现 简单类型 注入


使用 @Value 注解,将值写入注解的参数中就可以了

1
2
3
4
5
6
7
8
9
10
@Repository("bookDao")
public class BookDaoImpl implements BookDao {

@Value("heisenberg")
private String name;

public void save() {
System.out.println("book dao save ..." + name);
}
}

注解读取 properties配置文件


@Value 一般会被用在从 properties配置文件 中读取内容进行使用

名称 @Value
类型 属性注解 或 方法注解(了解)
位置 属性定义上方 或 标准set方法上方 或 类set方法上方
作用 为 基本数据类型 或 字符串类型 属性设置值
属性 value(默认):要注入的属性值

流程:

  1. resource 下准备 properties 文件

    1
    name=heisenberg
  2. 使用 注解 加载 properties配置文件

    • 在配置类上添加@PropertySource注解
    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan("com.itheima")
    @PropertySource("jdbc.properties")
    public class SpringConfig {
    }
  3. 使用 @Value 读取配置文件中的内容

    1
    2
    3
    4
    5
    6
    7
    8
    @Repository("bookDao")
    public class BookDaoImpl implements BookDao {
    @Value("${name}")
    private String name;
    public void save() {
    System.out.println("book dao save ..." + name);
    }
    }

注意:

  • 如果读取的properties配置文件有多个,可以使用@PropertySource的属性来指定多个

    1
    @PropertySource({"jdbc.properties","xxx.properties"})
  • @PropertySource注解属性中不支持使用通配符*,运行会报错

    1
    @PropertySource({"*.properties"})
  • @PropertySource注解属性中可以把classpath:加上,代表从当前项目的根路径找文件

    1
    @PropertySource({"classpath:jdbc.properties"})
名称 @PropertySource
类型 类注解
位置 类定义上方
作用 加载properties文件 中的属性值
属性 value(默认):设置加载的properties文件对应的文件名或文件名组成的数组

注解开发管理 第三方bean


名称 @Bean
类型 方法注解
位置 方法定义上方
作用 设置该方法的返回值作为spring管理的bean
属性 value(默认):定义bean的id

流程:

  1. 导入对应的 jar 包

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
    </dependency>
  2. 在 配置类中 添加一个方法,并在方法上添加 @Bean 注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class SpringConfig {
    @Bean
    public DataSource dataSource(){
    DruidDataSource ds = new DruidDataSource();
    ds.setDriverClassName("com.mysql.jdbc.Driver");
    ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
    ds.setUsername("root");
    ds.setPassword("root");
    return ds;
    }
    }
    • 不能使用DataSource ds = new DruidDataSource()
  3. 从 IOC容器 中获取对象并打印

    1
    2
    3
    4
    5
    6
    7
    public class App {
    public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    DataSource dataSource = ctx.getBean(DataSource.class);
    System.out.println(dataSource);
    }
    }
    • 如果有多个bean要被Spring管理,直接在配置类中多些几个方法,方法上添加 @Bean 注解即可

还有一种 扫描式 方案:可以不用加 @Configuration 注解,但是必须在Spring配置类上使用 @Import 注解手动引入需要加载的配置类

名称 @Import
类型 类注解
位置 类定义上方
作用 导入配置类
属性 value(默认):定义导入的配置类类名, 当配置类有多个时使用数组格式一次性导入多个配置类

流程:

  1. 去除JdbcConfig类上的 @Configuration 注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class JdbcConfig {
    @Bean
    public DataSource dataSource(){
    DruidDataSource ds = new DruidDataSource();
    ds.setDriverClassName("com.mysql.jdbc.Driver");
    ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
    ds.setUsername("root");
    ds.setPassword("root");
    return ds;
    }
    }
  2. 在 Spring配置类 中引入

    1
    2
    3
    4
    5
    6
    @Configuration
    //@ComponentScan("com.itheima.config")
    @Import({JdbcConfig.class})
    public class SpringConfig {

    }

注意:

  • 扫描注解可以移除

  • @Import 参数需要的是一个数组,可以引入多个配置类。

  • @Import 注解在配置类中 只能写一次


简单数据类型


使用@Value注解引入值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("password")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}

引用数据类型


流程:

  1. 在 SpringConfig 中扫描 BookDao

    • 扫描的目的是让 Spring 能管理到 BookDao,也就是说要让 IOC 容器中有一个 bookDao 对象
    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan("com.itheima.dao")
    @Import({JdbcConfig.class})
    public class SpringConfig {
    }
  2. 在 JdbcConfig 类的方法上添加参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Bean
    public DataSource dataSource(BookDao bookDao){
    System.out.println(bookDao);
    DruidDataSource ds = new DruidDataSource();
    ds.setDriverClassName(driver);
    ds.setUrl(url);
    ds.setUsername(userName);
    ds.setPassword(password);
    return ds;
    }
    • 引用类型注入 只需要为 bean 定义方法设置形参 即可,容器会根据类型自动装配对象

注解开发 总结


XML配置和注解配置的比较


Spring 整合 MyBatis


Mybatis 流程

Mybatis 流程图

步骤1:准备数据库表

Mybatis是来操作数据库表,所以先创建一个数据库及表

1
2
3
4
5
6
7
create database spring_db character set utf8;
use spring_db;
create table tbl_account(
id int primary key auto_increment,
name varchar(35),
money double
);

步骤2:创建项目导入jar包

项目的pom.xml添加相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>

步骤3:根据表创建模型类

1
2
3
4
5
6
7
public class Account implements Serializable {

private Integer id;
private String name;
private Double money;
//setter...getter...toString...方法略
}

步骤4:创建Dao接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface AccountDao {

@Insert("insert into tbl_account(name,money)values(#{name},#{money})")
void save(Account account);

@Delete("delete from tbl_account where id = #{id} ")
void delete(Integer id);

@Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
void update(Account account);

@Select("select * from tbl_account")
List<Account> findAll();

@Select("select * from tbl_account where id = #{id} ")
Account findById(Integer id);
}

步骤5:创建Service接口和实现类

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
33
34
35
36
37
38
39
40
public interface AccountService {

void save(Account account);

void delete(Integer id);

void update(Account account);

List<Account> findAll();

Account findById(Integer id);

}

@Service
public class AccountServiceImpl implements AccountService {

@Autowired
private AccountDao accountDao;

public void save(Account account) {
accountDao.save(account);
}

public void update(Account account){
accountDao.update(account);
}

public void delete(Integer id) {
accountDao.delete(id);
}

public Account findById(Integer id) {
return accountDao.findById(id);
}

public List<Account> findAll() {
return accountDao.findAll();
}
}

步骤6:添加jdbc.properties文件

resources目录下添加,用于配置数据库连接四要素

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root

useSSL:关闭MySQL的SSL连接

步骤7:添加Mybatis核心配置文件

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--读取外部properties配置文件-->
<properties resource="jdbc.properties"></properties>
<!--别名扫描的包路径-->
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>
<!--数据源-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!--映射文件扫描包路径-->
<mappers>
<package name="com.itheima.dao"></package>
</mappers>
</configuration>

步骤8:编写应用程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class App {
public static void main(String[] args) throws IOException {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

// 2. 加载SqlMapConfig.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml.bak");

// 3. 创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);

// 4. 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

// 5. 执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);

Account ac = accountDao.findById(1);
System.out.println(ac);

// 6. 释放资源
sqlSession.close();
}
}

整合 Mybatis

整合 Mybatis流程图

说明:

  • 第一行读取外部 properties 配置文件,Spring有提供具体的解决方案@PropertySource,需要交给Spring
  • 第二行起别名包扫描,为 SqlSessionFactory 服务的,需要交给 Spring
  • 第三行主要用于做连接池,Spring 之前我们已经整合了 Druid连接池,这块也需要交给 Spring
  • 前面三行一起都是为了创建SqlSession对象用的,只需要将SqlSessionFactory交给Spring管理即可。
  • 第四行是Mapper接口和映射文件[如果使用注解就没有该映射文件],这个是在获取到SqlSession以后执行具体操作的时候用,所以它和SqlSessionFactory创建的时机都不在同一个时间,可能需要单独管理。

流程:

  1. 创建项目导入 jar 包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <dependency>
    <!--Spring操作数据库需要该jar包-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
    <!--
    Spring与Mybatis整合的jar包
    这个jar包mybatis在前面,是Mybatis提供的
    -->
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.0</version>
    </dependency>
  2. 创建 Spring 的主配置类

    1
    2
    3
    4
    5
    6
    //配置类注解
    @Configuration
    //包扫描,主要扫描的是项目中的AccountServiceImpl类
    @ComponentScan("com.itheima")
    public class SpringConfig {
    }
  3. 创建数据源的配置类

    • 在配置类中完成数据源的创建
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
    DruidDataSource ds = new DruidDataSource();
    ds.setDriverClassName(driver);
    ds.setUrl(url);
    ds.setUsername(userName);
    ds.setPassword(password);
    return ds;
    }
    }
  4. 主配置类中读 properties 并引入数据源配置类

    1
    2
    3
    4
    5
    6
    @Configuration
    @ComponentScan("com.itheima")
    @PropertySource("classpath:jdbc.properties")
    @Import(JdbcConfig.class)
    public class SpringConfig {
    }
  5. 创建 Mybatis配置类 并配置 SqlSessionFactory

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class MybatisConfig {

    //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
    SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
    //设置模型类的别名扫描
    ssfb.setTypeAliasesPackage("com.itheima.domain");
    //设置数据源
    ssfb.setDataSource(dataSource);
    return ssfb;
    }

    //定义bean,返回 MapperScannerConfigurer 对象
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
    MapperScannerConfigurer msc = new MapperScannerConfigurer();
    msc.setBasePackage("com.itheima.dao");
    return msc;
    }
    }
    • SqlSessionFactoryBean 是 FactoryBean 的一个子类,在该类中将 SqlSessionFactory 的创建进行了封装,简化对象的创建,只需要将其需要的内容设置即可。

      SqlSessionFactoryBean

      • 方法中有一个参数为 dataSource,当前Spring容器中已经创建了Druid数据源,类型刚好是DataSource类型,此时在初始化SqlSessionFactoryBean这个对象的时候,发现需要使用DataSource对象,而容器中刚好有这么一个对象,就自动加载了 DruidDataSource 对象
    • MapperScannerConfigurer 对象也是MyBatis提供的专用于整合的jar包中的类,用来处理原始配置文件中的 mappers 相关配置,加载数据层的 Mapper 接口类

      MapperScannerConfigurer

      • MapperScannerConfigurer有一个核心属性 basePackage,就是用来设置所扫描的包路径
  6. 主配置类中引入 Mybatis配置类

    1
    2
    3
    4
    5
    6
    @Configuration
    @ComponentScan("com.itheima")
    @PropertySource("classpath:jdbc.properties")
    @Import({JdbcConfig.class,MybatisConfig.class})
    public class SpringConfig {
    }
  7. 编写运行类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class App {
    public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

    AccountService accountService = ctx.getBean(AccountService.class);

    Account ac = accountService.findById(1);
    System.out.println(ac);
    }
    }

支持Spring与Mybatis的整合就已经完成了,其中主要用到的两个类分别是:

  • SqlSessionFactoryBean
  • MapperScannerConfigurer
  1. Spring整合Junit

    名称 @RunWith
    类型 测试类注解
    位置 测试类定义上方
    作用 设置JUnit运行器
    属性 value(默认):运行所使用的运行期
    名称 @ContextConfiguration
    类型 测试类注解
    位置 测试类定义上方
    作用 设置JUnit加载的Spring核心配置
    属性 classes:核心配置类,可以使用数组的格式设定加载多个配置类 locations:配置文件,可以使用数组的格式设定加载多个配置文件名称
    • 引入依赖

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
      </dependency>

      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.10.RELEASE</version>
      </dependency>
    • 在 test\java 下创建一个 AccountServiceTest,这个名字任意

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      //设置类运行器
      @RunWith(SpringJUnit4ClassRunner.class)
      //设置Spring环境对应的配置类
      @ContextConfiguration(classes = {SpringConfiguration.class}) //加载配置类
      //@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载配置文件
      public class AccountServiceTest {

      //支持自动装配注入bean
      @Autowired
      private AccountService accountService;

      @Test
      public void testFindById(){
      System.out.println(accountService.findById(1));

      }

      @Test
      public void testFindAll(){
      System.out.println(accountService.findAll());
      }
      }
      • 单元测试,如果测试的是 注解配置类,则使用 @ContextConfiguration(classes = 配置类.class)
      • 单元测试,如果测试的是 配置文件,则使用 @ContextConfiguration(locations={配置文件名,...})
      • Junit 运行后是基于 Spring 环境运行的,所以 Spring 提供了一个专用的类运行器,这个务必要设置,这个类运行器就在 Spring 的测试专用包中提供的,导入的坐标就是这个东西SpringJUnit4ClassRunner
      • 上面两个配置都是固定格式,当需要测试哪个bean时,使用自动装配加载对应的对象,下面的工作就和以前做Junit单元测试完全一样了

AOP


AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构

  • OOP(Object Oriented Programming)面向对象编程

AOP的作用: 在不改原有代码的前提下对其进行功能增强

在不惊动(改动)原有设计(代码)的前提下,想给谁添加功能就给谁添加

  • 这个就是Spring的理念:无入侵式/无侵入式

AOP 核心概念


AOP案例

Spring 的 AOP 是对一个类的方法在 不进行任何修改的前提 下实现增强

对于上面的案例中:

  • BookServiceImpl 中有 save 、update 、 delete 和 select 方法,这些方法我们给起了一个名字叫 连接点
  • 在BookServiceImpl的四个方法中,update和delete只有打印 没有计算万次执行消耗时间,但是在运行的时候已经有该功能,那也就是说 update 和 delete 方法都已经被增强,所以对于需要增强的方法叫 切入点
  • 执行BookServiceImpl的 update 和 delete 方法的时候都被添加了一个计算万次执行消耗时间的功能,将这个功能抽取到一个方法中,换句话说就是 存放共性功能 的方法叫做 通知
  • 通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,那 哪个切入点需要添加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于 通知和切入点之间的关系 描述叫做 切面
  • 通知 是一个 方法,方法 不能独立存在需要被写在 一个类 中,这个类叫做 通知类

总结:

  • 代理 (proxy):SpringAOP的核心本质是采用 代理模式 实现的

  • 连接点 (JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等

    • 在SpringAOP中,理解为 任意方法的执行
  • 切入点 (Pointcut):匹配连接点的式子

    • 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法
    • 可以理解为:具有共性功能的方法描述
      • 一个具体的方法:如 com.itheima.dao 包下的 BookDao 接口中的无形参无返回值的 save 方法
      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
    • 连接点范围要比切入点范围大,是切入点的方法也一定是连接点,但是是连接点的方法就不一定要被增强,所以可能不是切入点。
  • 通知 (Advice):在切入点处执行的操作,也就是共性功能

    • 可以理解为:若干个方法的共性功能,在切入点处执行,最终体现为一个方法
    • 在SpringAOP中,功能最终 以方法的形式 呈现
  • 通知类:定义通知的类

  • 切面 (Aspect):描述 通知与切入点的对应关系

  • 目标对象 (target):被代理的原始对象称为目标对象


AOP 入门案例分析


采用 注解 完成 AOP的开发

思路分析:

  1. 导入坐标 (pom.xml)
  2. 制作连接点 (原始操作,Dao接口与实现类)
  3. 制作共性功能 (通知类与通知)
  4. 定义切入点
  5. 绑定切入点与通知关系 (切面)

流程:

  1. 添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
    </dependency>
    • 因为 spring-context 中已经导入了 spring-aop,所以不需要再单独导入spring-aop
    • 导入 AspectJ 的 jar 包,AspectJ是AOP思想的一个具体实现,Spring有自己的AOP实现,但是相比于AspectJ来说比较麻烦,所以我们直接采用Spring整合ApsectJ的方式进行AOP开发
  2. 添加 BookDao 和 BookDaoImpl 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public interface BookDao {
    public void save();
    public void update();
    }


    @Repository
    public class BookDaoImpl implements BookDao {

    public void save() {
    System.out.println(System.currentTimeMillis());
    System.out.println("book dao save ...");
    }

    public void update(){
    System.out.println("book dao update ...");
    }
    }
  3. 定义 通知类 和 通知

    • 通知 就是 将共性功能抽取出来后形成的方法,共性功能 指的就是 当前系统时间的打印
    1
    2
    3
    4
    5
    public class MyAdvice {
    public void method(){
    System.out.println(System.currentTimeMillis());
    }
    }
  4. 定义切入点

    名称 @Pointcut
    类型 方法注解
    位置 切入点方法定义上方
    作用 设置切入点方法
    属性 value(默认):切入点表达式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class MyAdvice {

    // 这块起始就是定义了一个位置
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    public void method(){
    System.out.println(System.currentTimeMillis());
    }
    }
    • 切入点 定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑
  5. 制作切面

    • 绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行 位置
    名称 @Before
    类型 方法注解
    位置 通知方法定义上方
    作用 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    public void method(){
    System.out.println(System.currentTimeMillis());
    }
    }
    • @Before 翻译过来是 之前,也就是说 通知会在切入点方法执行 之前执行

    切面

  6. 将通知类 配给 容器 并标识其为 切面类

    名称 @Aspect
    类型 类注解
    位置 切面类定义上方
    作用 设置当前类为AOP切面类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Component
    @Aspect
    public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    public void method(){
    System.out.println(System.currentTimeMillis());
    }
    }
  7. 开启注解格式 AOP 功能

    名称 @EnableAspectJAutoProxy
    类型 配置类注解
    位置 配置类定义上方
    作用 开启注解格式AOP功能
    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan("com.itheima")
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
  8. 运行程序

    1
    2
    3
    4
    5
    6
    7
    public class App {
    public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    BookDao bookDao = ctx.getBean(BookDao.class);
    bookDao.update();
    }
    }

AOP 工作流程


AOP 是 基于Spring容器管理的bean 做的增强

SpringAOP 本质:代理模式

  1. Spring容器启动

    • 容器启动就需要去加载bean,哪些类需要被加载呢?
    • 需要被增强的类,如:BookServiceImpl
    • 通知类,如:MyAdvice
    • 注意此时bean对象还没有创建成功
  2. 读取所有切面配置中的 切入点

    切入点配置

    • 如果有两个切入点的配置,但是第一个ptx()并没有被使用,则不会被读取
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点

    举例

    • 匹配失败,创建原始对象,如UserDao
    • 匹配成功,创建原始对象(目标对象)的 代理对象,如:BookDao
  4. 获取bean执行方法

    • 获取的bean是 原始对象 时,调用方法并执行,完成操作
    • 获取的bean是 代理对象 时,根据 代理对象的运行模式 运行 原始方法与增强的内容,完成操作

目标对象 (Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的

代理 (Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现


AOP 切入点表达式


切入点:要 进行增强 的 方法

切入点表达式:要 进行增强的方法 的 描述方式

eg:

案例

描述方式一:执行 com.itheima.dao 包下的 BookDao 接口中的 无参数update 方法

1
execution(void com.itheima.dao.BookDao.update())

描述方式二:执行 com.itheima.dao.impl 包下的 BookDaoImpl 类中的 无参数update 方法

1
execution(void com.itheima.dao.impl.BookDaoImpl.update())

因为调用接口方法的时候 最终运行的还是其实现类 的方法,所以上面两种描述方式都是可以的。

切入点表达式 的 标准格式:

1
动作关键字(访问修饰符  返回值  包名.类/接口名.方法名(参数) 异常名)

eg:

1
execution(public User com.itheima.service.UserService.findById(int))
  • execution:动作关键字,描述切入点的行为动作,例如 execution 表示 执行到指定切入点
  • public:访问修饰符,还可以是 public,private 等,可以省略
  • User:返回值,写返回值类型
  • com.itheima.service:包名,多级包使用点连接
  • UserService:类/接口名称
  • findById:方法名
  • int:参数,直接写参数的类型,多个类型用逗号隔开
  • 异常名:方法定义中抛出指定异常,可以省略

AOP 切入点表达式 的 通配符

使用通配符描述切入点,主要的目的就是简化之前的配置,具体的通配符有:

  • *:单个独立 的 任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

    1
    execution(public * com.itheima.*.UserService.find*(*))
    • 匹配 com.itheima 包下的 任意包 中的 UserService类或接口 中所有 find 开头的 带有一个参数 的方法
  • ..:多个连续 的 任意符号,可以独立出现,常用于简化包名与参数的书写

    1
    execution(public User com..UserService.findById(..))
    • 匹配 com 包下的 任意包 中的 UserService类或接口 中 所有名称 为 findById 的方法
  • +:专用于匹配子类类型

    1
    execution(* *..*Service+.*(..))
    • 这个使用率较低,描述子类的,继承机会就一次,使用都很慎重,很少使用
    • *Service+,表示所有以 Service 结尾的接口的子类

书写技巧:

  • 所有代码按照标准规范开发,否则以下技巧全部失效
  • 描述切入点 通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
  • 访问控制修饰符针对接口开发均采用 public 描述( 可省略访问控制修饰符描述)
  • 返回值类型对于 增删改 类使用 精准类型加速匹配,对于查询类使用 * 通配快速描述
  • 包名 书写 尽量不使用 … 匹配,效率过低,常用 * 做单个包描述匹配,或精准匹配
  • 接口名/类名 书写名称与模块相关的 采用 * 匹配,例如 UserService 书写成 *Service,绑定业务层接口名
  • 方法名 书写以 动词 进行 精准匹配,名词采用*匹配,例如 getById 书写成 getBy*,selectAll书写成selectAll
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常 不使用异常 作为 匹配 规则

AOP 通知类型


AOP 通知分为 5 种类型:

  • 前置通知 @Before()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Component
    @Aspect
    public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    //此处也可以写成 @Before("MyAdvice.pt()"),不建议
    public void before() {
    System.out.println("before advice ...");
    }
    }
  • 后置通知 @After()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Component
    @Aspect
    public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    public void before() {
    System.out.println("before advice ...");
    }
    @After("pt()")
    public void after() {
    System.out.println("after advice ...");
    }
    }
  • 环绕通知 @Around()

    • 环绕通知功能比较强大,它可以 追加功能到 方法执行的前后
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Component
    @Aspect
    public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    @Around("pt()")
    public void around(ProceedingJoinPoint pjp) throws Throwable{

    System.out.println("around before advice ...");

    //表示对原始操作的调用
    pjp.proceed();

    System.out.println("around after advice ...");
    }
    }
  • 返回后通知 (了解) AfterReturning()

  • 抛出异常后通知 (了解) @AfterThrowing()

几种通知类型的位置

@Around() 注意事项:

  1. 环绕通知必须依赖形参 ProceedingJoinPoint 才能实现 对原始方法的调用,进而实现原始方法调用前后同时添加通知
  2. 通知中如果 未使用ProceedingJoinPoint 对原始方法进行调用 将跳过原始方法 的执行
  3. 对原始方法的调用可以不接收返回值,通知方法设置成 void 即可,如果接收返回值,最好设定为 Object类型
  4. 原始方法的返回值如果是 void 类型,通知方法的返回值类型可以设置成 void,也可以设置成 Object
  5. 由于 无法预知原始方法运行后 是否会抛出异常,因此环绕通知方法必须要处理 Throwable 异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
@Aspect
public class MyAdvice {

@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}

@Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");

// 表示对原始操作的调用
Object ret = pjp.proceed();

// 表示获取这个原始操作的方法名
Signature signature = pjp.getSignature();
// signature.getName();
// signature.getDeclaringType();

System.out.println("around after advice ...");
return ret;
}
}
  • 返回的是 Object 而不是 int 的主要原因:Object 类型 更通用

AOP 通知获取数据


获取切入点方法的参数,所有的通知类型都可以获取参数

  • JoinPoint:适用于前置、后置、返回后、抛出异常后通知

    1
    2
    3
    4
    5
    6
    @Before("pt()")
    public void before(JoinPoint jp)
    Object[] args = jp.getArgs();
    System.out.println(Arrays.toString(args));
    System.out.println("before advice ..." );
    }
  • ProceedingJoinPoint:适用于 环绕通知

    1
    2
    3
    4
    5
    6
    7
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp)throws Throwable {
    Object[] args = pjp.getArgs();
    System.out.println(Arrays.toString(args));
    Object ret = pjp.proceed();
    return ret;
    }

获取切入点方法返回值,前置 和 抛出异常后通知 是没有返回值,后置通知可有可无,所以不做研究

  • 返回后通知,可以获得切入点方法中出现的异常信息,使用形参可以接收对应的异常对象

    1
    2
    3
    4
    @AfterReturning(value = "pt()",returning = "ret")
    public void afterReturning(Object ret) {
    System.out.println("afterReturning advice ..."+ret);
    }
  • 环绕通知,可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
    // 获取参数
    Object[] args = pjp.getArgs();
    System.out.println(Arrays.toString(args));
    args[0] = 666;

    Object ret = pjp.proceed(args);
    return ret;
    }

获取切入点方法运行异常信息,前置 和 返回后通知 是不会有,后置通知 可有可无,所以不做研究

  • 抛出异常后通知

    1
    2
    3
    4
    @AfterThrowing(value = "pt()",throwing = "t")
    public void afterThrowing(Throwable t) {
    System.out.println("afterThrowing advice ..."+t);
    }
  • 环绕通知

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
    Object[] args = pjp.getArgs();
    System.out.println(Arrays.toString(args));
    Object ret = null;
    try{
    ret = pjp.proceed(args);
    }catch(Throwable throwable){
    t.printStackTrace();
    }
    return ret;
    }

Spring 事务


事务 作用:在 数据层 保障一系列的数据库操作同成功同失败

Spring事务 作用:在 数据层 或 业务层 保障一系列的数据库操作同成功同失败

Spring为了管理事务,提供了一个平台事务管理器 PlatformTransactionManager

1
2
3
4
5
6
7
public interface PlatformTransactionManager extends TransactionManager {
// 事务提交
void commit(TransactionStatus var1) throws TransactionException;

// 事务回滚
void rollback(TransactionStatus var1) throws TransactionException;
}

PlatformTransactionManager 只是一个 接口,Spring 还为其提供了一个 具体的实现

1
2
3
public class DataSourceTransactionManager {
....
}

从名称上可以看出,只需要给它一个 DataSource 对象,它就可以在 业务层 管理事务。其内部采用的是JDBC的事务。所以说如果 持久层 采用的是 JDBC 相关的技术,就可以采用这个事务管理器来管理事务。而Mybatis内部采用的就是 JDBC 的事务,所以Spring整合 Mybatis 就采用的这个 DataSourceTransactionManager 事务管理器


Spring事务 管理流程


  1. 在需要 被事务管理的方法 上 添加注解

    名称 @Transactional
    类型 接口注解 类注解 方法注解
    位置 业务层接口上方 业务层实现类上方 业务方法上方
    作用 为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public interface AccountService {
    /**
    * 转账操作
    * @param out 传出方
    * @param in 转入方
    * @param money 金额
    */
    //配置当前接口方法具有事务
    public void transfer(String out,String in ,Double money) ;
    }

    @Service
    public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
    @Transactional
    public void transfer(String out,String in ,Double money) {
    accountDao.outMoney(out,money);
    int i = 1/0;
    accountDao.inMoney(in,money);
    }

    }
    • @Transactional 可以写在 接口类上、接口方法上、实现类上 和 实现类方法上
      • 写在接口类上,该接口的所有实现类的所有方法都会有事务
      • 写在接口方法上,该接口的所有实现类的该方法都会有事务
      • 写在实现类上,该类中的所有方法都会有事务
      • 写在实现类方法上,该方法上有事务
      • 建议写在 实现类 或 实现类的方法上
  2. 在 JdbcConfig 类中配置事务管理器

    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
    public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
    DruidDataSource ds = new DruidDataSource();
    ds.setDriverClassName(driver);
    ds.setUrl(url);
    ds.setUsername(userName);
    ds.setPassword(password);
    return ds;
    }

    //配置事务管理器,mybatis使用的是jdbc事务
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
    }
    }
  3. 在 SpringConfig 的配置类中开启

    名称 @EnableTransactionManagement
    类型 配置类注解
    位置 配置类定义上方
    作用 设置当前Spring环境中开启注解式事务支持
    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    @ComponentScan("com.itheima")
    @PropertySource("classpath:jdbc.properties")
    @Import({JdbcConfig.class,MybatisConfig.class
    //开启注解式事务驱动
    @EnableTransactionManagement
    public class SpringConfig {
    }
    • 事务管理器要根据使用技术进行选择,Mybatis 框架使用的是 JDBC 事务,可以直接使用DataSourceTransactionManager

Spring事务 角色


Spring事务角色

  • 事务管理员:发起事务方,在Spring中通常指代业务层 开启事务的方法
  • 事务协调员:加入事务方,在Spring中通常指代 数据层方法,也可以是 业务层方法

Spring事务 属性


事务属性配置

注意:

Spring的事务只会对 Error异常 和 RuntimeException异常 及其子类进行事务回滚,其他的异常类型是不会回滚的


事务的传播行为


事务的传播行为:事务协调员对事务管理员所携带事务的处理态度,涉及到 propagation属性

流程:

  1. 修改 logService 改变事务的传播行为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Service
    public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;

    //propagation设置事务属性:传播行为设置为REQUIRES_NEW(当前操作需要新事务)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String out,String in,Double money ) {
    logDao.log("转账操作由"+ out +"到"+ in +",金额:"+money);
    }
    }
  2. 事务传播行为 的 可选值

    事务传播行为 的 可选值


SpringMVC


SpringMVC流程图

SpringMVC 概述


SpringMVC是 一种 基于Java实现 MVC模型 的 轻量级Web框架

优点:

  1. 使用简单、开发便捷(相比于 Servlet)
  2. 灵活性强

SpringMVC 入门案例


流程:

  1. 导入 jar 包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <dependencies>
    <dependency>
    <!-- servlet的坐标 -->
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    </dependency>

    <dependency>
    <!-- springmvc的坐标 -->
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency>
    </dependencies>
  2. 创建SpringMVC控制器类(等同于Servlet功能)

    名称 @Controller
    类型 类注解
    位置 SpringMVC控制器类定义上方
    作用 设定SpringMVC的核心控制器bean
    名称 @RequestMapping
    类型 类注解 或 方法注解
    位置 SpringMVC控制器类或方法定义上方
    作用 设置当前控制器方法 请求访问路径,配置映射路径
    相关属性 value(默认),请求访问路径
    名称 @ResponseBody
    类型 类注解 或 方法注解
    位置 SpringMVC控制器类或方法定义上方
    作用 设置当前控制器方法响应内容为 当前返回值,无需解析,返回json数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 使用@controller定义bean
    @Controller
    public class UserController {

    // 设置当前操作的访问路径
    @RequestMapping("/save")

    // 设置当前操作的返回值类型
    @ResponseBody
    public String save(){
    System.out.println("user save ...");
    return "{'info':'springmvc'}";
    }
    }
  3. 初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应的bean

    1
    2
    3
    4
    @Configuration
    @ComponentScan("com.itheima.controller")
    public class SpringMvcConfig {
    }
  4. 初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 定义一个servlet容器启动的配置类,在里面加载spring的配置
    public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {

    // 加载springmvc容器配置
    protected WebApplicationContext createServletApplicationContext() {

    // 初始化WebApplicationContext对象
    AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();

    // 加载指定配置类
    ctx.register(SpringMvcConfig.class);
    return ctx;
    }

    // 设置哪些请求归属springmvc处理
    protected String[] getServletMappings() {
    return new String[]{"/"};
    }

    // 加载spring容器配置
    protected WebApplicationContext createRootApplicationContext() {
    return null;
    }
    }

SpringMVC入门案例总结:(1 + N)

  • 一次性工作

    • 创建工程,设置服务器,加载工程
    • 导入坐标
    • 创建 web容器 启动类,加载 SpringMVC配置,并设置 SpringMVC请求拦截路径
    • SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
  • 多次工作

    • 定义处理请求的控制器类
    • 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)
  • AbstractDispatcherServletInitializer类 是 SpringMVC 提供的 快速初始化Web3.0容器 的 抽象类

  • AbstractDispatcherServletInitializer 提供了 三个接口方法 供用户实现:

    • createServletApplicationContext() 方法:创建Servlet容器时,加载SpringMVC对应的bean并放入 WebApplicationContext 对象范围中,而 WebApplicationContext 的作用范围为 ServletContext 范围,即整个web容器范围

    • createServletApplicationContext:用来加载 SpringMVC 环境

    • getServletMappings() 方法:设定 SpringMVC 对应的 请求映射路径,即 SpringMVC 拦截哪些请求

      1
      2
      3
      4
      // 设置为 / 表示拦截所有请求,任意请求都将转入SpringMVC处理
      protected String[] getServletMappings() {
      return new String[]{"/"};
      }
    • createRootApplicationContext() 方法:如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式和 createServletApplicationContext 相同

    • createRootApplicationContext:用来加载 Spring 环境

工作流程解析


SpringMVC 工作流程


启动服务器初始化过程


流程:

  1. 服务器启动,执行 ServletContainersInitConfig 类,初始化web容器

    • 功能类似于以前的 web.xml
  2. 执行 createServletApplicationContext 方法,创建了 WebApplicationContext 对象

    • 该方法加载 SpringMVC 的配置类 SpringMvcConfig 来初始化 SpringMVC 的容器
  3. 加载 SpringMvcConfig 配置类

  4. 执行 @ComponentScan 加载对应的 bean

    • 扫描指定包及其子包下所有类上的注解,如 Controller 类上的 @Controller 注解
  5. 加载 UserController,每个 @RequestMapping 的名称对应一个具体的方法

    • 此时就建立了 /save 和 save 方法的对应关系
  6. 执行 getServletMappings 方法,设定 SpringMVC 拦截请求的路径规则

    • /代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求

单次请求过程


流程:

  1. 发送请求 http://localhost/save
  2. web容器发现该请求满足 SpringMVC 拦截规则,将请求交给 SpringMVC 处理
  3. 解析请求路径 /save
  4. 由 /save 匹配执行对应的方法 save()
    • 上面的第五步已经将请求路径和方法建立了对应关系,通过 /save 就能找到对应的 save 方法
  5. 执行 save()
  6. 检测到有 @ResponseBody 直接将 save() 方法的返回值作为响应体返回给请求方

bean 加载控制


现在存在的问题:

  • SpringMVC 加载其相关 bean (表现层bean),也就是 controller 包下的类
  • Spring 控制的 bean
    • 业务 bean (Service)
    • 功能 bean (DataSource、SqlSessionFactoryBean、MapperScannerConfigurer等)
  • 如何让 Spring 和 SpringMVC 分开加载各自的内容?
    • 答:加载 Spring 控制的 bean 的时候排除掉 SpringMVC 控制的 bean

方案:

名称 @ComponentScan
类型 类注解
位置 类定义上方
作用 设置spring配置类扫描路径,用于加载使用注解格式定义的bean
相关属性 excludeFilters:排除扫描路径中加载的bean,需要指定类别 (type) 和具体项 (classes) includeFilters:加载指定的bean,需要指定类别 (type) 和具体项 (classes)
  • 方式一:Spring 加载的 bean 设定扫描范围为 精准范围,例如service包、dao包等

    1
    2
    3
    4
    @Configuration
    @ComponentScan({"com.itheima.service","comitheima.dao"})
    public class SpringConfig {
    }
    • Dao最终是交给 MapperScannerConfigurer 对象来进行扫描处理的,只需要将其扫描到service包即可
  • 方式二:Spring 加载的 bean 设定扫描范围为 com.itheima,排除掉 controller 包中的 bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    @ComponentScan(value="com.itheima",
    excludeFilters=@ComponentScan.Filter(
    type = FilterType.ANNOTATION,
    classes = Controller.class
    )
    )
    public class SpringConfig {
    }
    • excludeFilters 属性:设置扫描加载 bean 时,排除的 过滤规则

    • type属性:设置 排除规则,当前使用按照 bean 定义时的注解类型进行排除

      • ANNOTATION:按照注解排除(常用)
      • ASSIGNABLE_TYPE:按照指定的类型过滤
      • ASPECTJ:按照 Aspectj 表达式排除,基本上不会用
      • REGEX:按照正则表达式排除
      • CUSTOM:按照自定义规则排除
    • classes属性:设置 排除的具体注解类,当前设置排除 @Controller 定义的 bean

  • 方式三:不区分 Spring 与 SpringMVC 的环境,加载到同一个环境中(了解)

有了Spring的配置类,要想在 tomcat服务器 启动将其加载,需要修改ServletContainersInitConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {

protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}

protected String[] getServletMappings() {
return new String[]{"/"};
}

protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}

对于上述的配置方式,Spring还提供了一种更简单的配置方式,可以不用再去创建AnnotationConfigWebApplicationContext 对象,不用手动register对应的配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}

protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}

protected String[] getServletMappings() {
return new String[]{"/"};
}
}

请求 与 响应


请求 的 映射路径


设置映射路径的流程:

  1. 修改 Controller

    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
    @Controller
    public class UserController {

    @RequestMapping("/user/save")
    @ResponseBody
    public String save(){
    System.out.println("user save ...");
    return "{'module':'user save'}";
    }

    @RequestMapping("/user/delete")
    @ResponseBody
    public String save(){
    System.out.println("user delete ...");
    return "{'module':'user delete'}";
    }
    }

    @Controller
    public class BookController {

    @RequestMapping("/book/save")
    @ResponseBody
    public String save(){
    System.out.println("book save ...");
    return "{'module':'book save'}";
    }
    }
  2. 优化路径配置

    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
    @Controller
    @RequestMapping("/user")
    public class UserController {

    @RequestMapping("/save")
    @ResponseBody
    public String save(){
    System.out.println("user save ...");
    return "{'module':'user save'}";
    }

    @RequestMapping("/delete")
    @ResponseBody
    public String save(){
    System.out.println("user delete ...");
    return "{'module':'user delete'}";
    }
    }

    @Controller
    @RequestMapping("/book")
    public class BookController {

    @RequestMapping("/save")
    @ResponseBody
    public String save(){
    System.out.println("book save ...");
    return "{'module':'book save'}";
    }
    }

注意:

  • 当类上和方法上都添加了@RequestMapping注解,前端发送请求的时候,要和两个注解的 value 值相加匹配才能访问到
  • @RequestMapping 注解 value 属性前面加不加 / 都可以

请求参数


Get 请求

Post 请求

解决Post请求中文乱码问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}

protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}

protected String[] getServletMappings() {
return new String[]{"/"};
}

//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}

如果前端给的是name,后台接收使用的是userName,两个名称对不上,导致接收数据失败,使用@RequestParam 注解

名称 @RequestParam
类型 形参注解
位置 SpringMVC控制器方法形参定义前面
作用 绑定请求参数与处理器方法形参间的关系
相关参数 required:是否为必传参数 defaultValue:参数默认值
1
2
3
4
5
6
7
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestPaam("name") String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}

JSON数据 传输参数


对于JSON数据类型,我们常见的有三种:

  • json普通数组
    • ([“value1”,“value2”,“value3”,…])
  • json对象
    • ({key1:value1,key2:value2,…})
  • json对象数组
    • ([{key1:value1,…},{key2:value2,…}])

流程:

  1. pom.xml 添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
    </dependency>
  2. 开启 SpringMVC 注解支持

    名称 @EnableWebMvc
    类型 配置类注解
    位置 SpringMVC配置类定义上方
    作用 开启SpringMVC多项辅助功能
    1
    2
    3
    4
    5
    6
    @Configuration
    @ComponentScan("com.itheima.controller")
    //开启json数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
  3. 参数前添加 @RequestBody

    名称 @RequestBody
    类型 形参注解
    位置 SpringMVC控制器方法形参定义前面
    作用 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次
    1
    2
    3
    4
    5
    6
    7
    // 使用@RequestBody注解 将外部传递的json数组数据 映射到 形参的集合对象中 作为数据
    @RequestMapping("/listParamForJson")
    @ResponseBody
    public String listParamForJson(@RequestBody List<String> likes){
    System.out.println("list common(json)参数传递 list ==> "+likes);
    return "{'module':'list common for json param'}";
    }

@RequestBody 与 @RequestParam 区别:

  • 区别

    • @RequestParam 用于 接收 url地址 传参,表单传参【application/x-www-form-urlencoded】
    • @RequestBody 用于 接收 json 数据【application/json】
  • 应用

    • 后期开发中,发送 json 格式数据为主,@RequestBody 应用较广
    • 如果发送 非json 格式数据,选用 @RequestParam 接收请求参数

日期类型 参数传递


SpringMVC 默认支持的 字符串转日期 的格式为 yyyy/MM/dd,但如果发送 yyyy-MM-dd,不符合其默认格式,SpringMVC 就无法进行格式转换,解决办法是使用 @DateTimeFormat注解

名称 @DateTimeFormat
类型 ==形参注解==
位置 SpringMVC控制器方法形参前面
作用 设定日期时间型数据格式
相关属性 pattern:指定日期时间格式字符串

eg:

1
2
3
4
5
6
7
8
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
@DateTimeFormat(pattern="yyyy-MM-dd") Date date1)
System.out.println("参数传递 date ==> "+date);
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
return "{'module':'data param'}";
}

内部实现原理:

SpringMVC 中提供了 很多类型转换接口和实现类,例如:Converter接口

  • Converter 所属的包为 org.springframework.core.convert.converter
1
2
3
4
5
6
7
8
9
/**
* S: the source type
* T: the target type
*/
public interface Converter<S, T> {
@Nullable
//该方法就是将从页面上接收的数据(S)转换成我们想要的数据类型(T)返回
T convert(S source);
}

HttpMessageConverter 接口

  • 该接口是实现 对象与JSON 之间的转换工作

SpringMVC的配置类把 @EnableWebMvc 当做标配配置上去


响应 JSON数据


名称 @ResponseBody
类型 方法 / 类注解
位置 SpringMVC控制器方法定义上方和控制类上
作用 设置当前控制器返回值作为响应体,写在类上,该类的所有方法都有该注解功能
相关属性 pattern:指定日期时间格式字符串

响应POJO对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class UserController {

@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
System.out.println("返回json对象数据");
User user = new User();
user.setName("itcast");
user.setAge(15);
return user;
}

}
  • 返回值为 实体类 对象,设置返回值为 实体类 类型,即可实现返回对应对象的 json 数据,需要依赖@ResponseBody 注解和 @EnableWebMvc 注解

响应POJO集合对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Controller
public class UserController {

@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
System.out.println("返回json集合数据");
User user1 = new User();
user1.setName("传智播客");
user1.setAge(15);

User user2 = new User();
user2.setName("黑马程序员");
user2.setAge(12);

List<User> userList = new ArrayList<User>();
userList.add(user1);
userList.add(user2);

return userList;
}

}

@ResponseBody注解说明:

  • 该注解可以写在 类上 或者 方法上
  • 写在类上就是该类下的所有方法都有 @ReponseBody 功能
  • 当方法上有 @ReponseBody 注解后
    • 方法的返回值为 字符串,会将其作为 文本内容 直接响应给 前端
    • 方法的返回值为 对象,会将 对象 转换成 JSON 响应给 前端

此处又使用到了类型转换,内部还是通过 Converter接口 (HttpMessageConverter)的实现类完成的,所以Converter除了前面所说的功能外,它还可以实现:

  • 对象 转 Json数据 (POJO -> json)
  • 集合 转 Json数据 (Collection -> json)

Rest 风格


REST(Representational State Transfer),表现形式状态转换,它是一种软件架构 风格

当想表示一个网络资源的时候,可以使用两种方式:

  • 传统风格 资源描述形式
    • http://localhost/user/getById?id=1 查询id为1的用户信息
    • http://localhost/user/saveUser 保存用户信息
  • REST风格 描述形式
    • http://localhost/user/1
    • http://localhost/user

REST的优点有:

  • 隐藏资源 的 访问行为,无法通过 地址 得知对资源是何种操作
  • 书写简化

按照 REST风格 访问资源时使用 行为动作 区分对资源进行了何种操作

  • http://localhost/users 查询全部用户信息 GET(查询)
  • http://localhost/users/1 查询指定用户信息 GET(查询)
  • http://localhost/users 添加用户信息 POST(新增 / 保存)
  • http://localhost/users 修改用户信息 PUT(修改 / 更新)
  • http://localhost/users/1 删除用户信息 DELETE(删除)

请求的方式比较多,但是比较常用的就4种,分别是GET、POST、PUT、DELETE。

按照 不同的请求方式 代表不同的操作类型:

  • 发送 GET 请求是用来做 查询
  • 发送 POST 请求是用来做 新增
  • 发送 PUT 请求是用来做 修改
  • 发送 DELETE 请求是用来做 删除

根据 REST风格 对资源进行访问称为 RESTful

注意:

上述行为是约定方式,约定不是规范,可以打破,所以称 REST风格,而不是REST规范

  • REST提供了对应的架构方式,按照这种架构设计项目 可以降低开发的复杂性,提高系统的可伸缩性
  • REST中规定 GET / POST / PUT / DELETE 针对的是 查询 / 新增 / 修改 / 删除,但是我们如果非要用 GET 请求做删除,这点在程序上运行是可以实现的
  • 描述模块的名称通常使用复数,也就是加 s 的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts…
名称 @PathVariable
类型 形参注解
位置 SpringMVC控制器方法形参定义前面
作用 绑定路径参数 与 处理器方法形参 间的关系,要求路径参数名与形参名一一对应

eg:

1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {
//设置当前请求方法为GET,表示REST风格中的查询操作
@RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("user getById..."+id);
return "{'module':'user getById'}";
}
}

@RequestBody、@RequestParam、@PathVariable的区别:

  • 区别
    • @RequestParam 用于 接收 url地址 传参 或 表单传参
    • @RequestBody 用于 接收 json 数据
    • @PathVariable 用于 接收 路径参数,使用 {参数名称} 描述 路径参数
  • 应用
    • 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
    • 如果发送 非json格式 数据,选用 @RequestParam 接收请求参数
    • 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用 @PathVariable 接收请求路径变量,通常用于 传递id值

Restful 风格案例


名称 @RestController
类型 类注解
位置 基于SpringMVC的RESTful开发控制器类定义上方
作用 设置当前控制器类为 RESTful风格, 等同于 @Controller 与 @ResponseBody 两个注解组合功能
名称 @GetMapping @PostMapping @PutMapping @DeleteMapping
类型 方法注解
位置 基于 SpringMVC 的 RESTful 开发控制器方法定义上方
作用 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作, 例如: @GetMapping 对应GET请求
相关属性 value(默认):请求访问路径

eg:

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
33
34
35
36
37
38
39
40
@RestController //@Controller + ReponseBody
@RequestMapping("/books")
public class BookController {

//@RequestMapping(method = RequestMethod.POST)
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}

//@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}

//@RequestMapping(method = RequestMethod.PUT)
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}

//@RequestMapping(value = "/{id}",method = RequestMethod.GET)
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}

//@RequestMapping(method = RequestMethod.GET)
@GetMapping
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}

}

SpringMVC 页面访问处理


流程:

  1. 将静态页面放到 项目的webapp目录下

  2. SpringMVC 会 拦截静态资源,SpringMVC需要将 静态资源 进行 放行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport {

    // 设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {

    // 当访问请求为 /pages/???? 时候不要走Mvc,从 /pages 目录下 查找内容
    registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    registry.addResourceHandler("/css/**").addResourceLocations("/css/");
    registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
    }
    • 该配置类是在 config 目录下,SpringMVC 扫描的是 controller 包,所以该配置类还未生效,要想生效需要将 SpringMvcConfig 配置类进行修改
  3. SpringMvcConfig 配置类进行修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Configuration
    @ComponentScan({"com.itheima.controller","com.itheima.config"})
    @EnableWebMvc
    public class SpringMvcConfig {
    }

    // 或者

    @Configuration
    @ComponentScan("com.itheima")
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    • Spring的容器不能访问SpringMVC的容器,SpringMVC的容器可以访问Spring的容器

SSM 整合


流程分析


  1. 创建Maven工程
    • 编写Web项目的入口配置类,实现AbstractAnnotationConfigDispatcherServletInitializer重写以下方法
      • getRootConfigClasses():返回Spring的配置类
        • 需要 SpringConfig 配置类
      • getServletConfigClasses() :返回SpringMVC的配置类
        • 需要 SpringMvcConfig 配置类
      • getServletMappings():设置SpringMVC请求拦截路径规则
      • getServletFilters() :设置过滤器,解决POST请求中文乱码问题
  2. SSM整合
    • SpringConfig
      • 标识该类为 配置类 @Configuration
      • 扫描 Service 所在的包 @ComponentScan
      • 在Service 层要 管理事务 @EnableTransactionManagement
      • 读取外部的 properties 配置文件 @PropertySource
      • 整合Mybatis需要引入Mybatis相关配置类 @Import
        • 第三方数据源配置类 JdbcConfig
          • 构建DataSource数据源,DruidDataSouroce,需要注入数据库连接四要素,@Bean @Value
          • 构建平台事务管理器,DataSourceTransactionManager,@Bean
        • Mybatis配置类 MybatisConfig
          • 构建SqlSessionFactoryBean并设置别名扫描与数据源,@Bean
          • 构建MapperScannerConfigurer并设置DAO层的包扫描
    • SpringMvcConfig
      • 标识该类为配置类 @Configuration
      • 扫描Controller所在的包 @ComponentScan
      • 开启SpringMVC注解支持 @EnableWebMvc
  3. 功能模块
    • 创建 数据库表
    • 根据 数据库表 创建对应的 模型类
    • 通过 Dao 层完成数据库表的增删改查(接口 + 自动代理)
    • 编写Service层(Service接口 + 实现类)
      • @Service
      • @Transactional
      • 整合Junit对业务层进行单元测试
        • @RunWith
        • @ContextConfiguration
        • @Test
    • 编写Controller层
      • 接收请求 @RequestMapping、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping
      • 接收数据 简单、POJO、嵌套POJO、集合、数组、JSON数据类型
        • @RequestParam
        • @PathVariable
        • @RequestBody
      • 转发业务层
        • @Autowired
      • 响应结果
        • @ResponseBody

整合配置


流程:

  1. 添加依赖

    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
    </dependency>

    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.0</version>
    </dependency>

    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    </dependency>

    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
    </dependency>

    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>

    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    </dependency>

    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
    </dependency>
    </dependencies>
  2. 创建项目包结构

    • config 目录存放的是 相关的配置类
    • controller 编写的是 Controller 类
    • dao 存放的是 Dao 接口,因为使用的是 Mapper接口代理 方式,所以没有实现类包
    • service 存的是 Service 接口,impl 存放的是 Service实现类
    • resources 存入的是配置文件,如:Jdbc.properties
    • webapp 目录可以存放 静态资源
    • test/java 存放的是 测试类
  3. 创建 SpringConfig 配置类

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    @ComponentScan({"com.itheima.service"})
    @PropertySource("classpath:jdbc.properties")
    @Import({JdbcConfig.class,MyBatisConfig.class})
    @EnableTransactionManagement
    public class SpringConfig {
    }
  4. 创建 JdbcConfig 配置类

    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
    public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName(driver);
    dataSource.setUrl(url);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
    DataSourceTransactionManager ds = new DataSourceTransactionManager();
    ds.setDataSource(dataSource);
    return ds;
    }
    }
  5. 创建 MybatisConfig 配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    factoryBean.setTypeAliasesPackage("com.itheima.domain");
    return factoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
    MapperScannerConfigurer msc = new MapperScannerConfigurer();
    msc.setBasePackage("com.itheima.dao");
    return msc;
    }
    }
  6. 创建 jdbc.properties

    1
    2
    3
    4
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/ssm_db
    jdbc.username=root
    jdbc.password=root
  7. 创建 SpringMVC 配置类

    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan("com.itheima.controller")
    @EnableWebMvc
    public class SpringMvcConfig {
    }
  8. 创建 Web项目 入口配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    //加载Spring配置类
    protected Class<?>[] getRootConfigClasses() {
    return new Class[]{SpringConfig.class};
    }
    //加载SpringMVC配置类
    protected Class<?>[] getServletConfigClasses() {
    return new Class[]{SpringMvcConfig.class};
    }
    //设置SpringMVC请求地址拦截规则
    protected String[] getServletMappings() {
    return new String[]{"/"};
    }
    //设置post请求中文乱码过滤器
    @Override
    protected Filter[] getServletFilters() {
    CharacterEncodingFilter filter = new CharacterEncodingFilter();
    filter.setEncoding("utf-8");
    return new Filter[]{filter};
    }
    }

统一结果封装


表现层数据封装


为了封装返回的结果数据:创建结果模型类,封装数据到 data 属性中

为了封装返回的数据是何种操作及是否操作成功:封装操作结果到code属性中

操作失败后为了封装返回的错误信息:封装特殊消息到 message(msg) 属性中

所以,设置统一数据返回结果类:

1
2
3
4
5
public class Result{
private Object data;
private Integer code;
private String msg;
}

注意:Result类名及类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作


结果封装


对于结果封装,应该是在 表现层 进行处理,所以把 结果类 放在 controller包 下

流程:

  1. 创建 Result 类

    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
    public class Result {

    // 描述统一格式中的数据
    private Object data;

    // 描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败
    private Integer code;

    // 描述统一格式中的消息,可选属性
    private String msg;

    public Result() {
    }

    // 构造方法是方便对象的创建
    public Result(Integer code,Object data) {
    this.data = data;
    this.code = code;
    }

    // 构造方法是方便对象的创建
    public Result(Integer code, Object data, String msg) {
    this.data = data;
    this.code = code;
    this.msg = msg;
    }

    // setter...getter...省略
    }
  2. 定义返回码 Code 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //状态码
    public class Code {
    public static final Integer SAVE_OK = 20011;
    public static final Integer DELETE_OK = 20021;
    public static final Integer UPDATE_OK = 20031;
    public static final Integer GET_OK = 20041;

    public static final Integer SAVE_ERR = 20010;
    public static final Integer DELETE_ERR = 20020;
    public static final Integer UPDATE_ERR = 20030;
    public static final Integer GET_ERR = 20040;
    }
    • 注意:code类中的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为GET_OK、GET_ALL_OK、GET_PAGE_OK等
  3. 修改 Controller 类的返回值

    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    // 统一每一个控制器方法返回值
    @RestController
    @RequestMapping("/books")
    public class BookController {

    @Autowired
    private BookService bookService;

    @PostMapping
    public Result save(@RequestBody Book book) {
    boolean flag = bookService.save(book);
    return new Result(flag ? Code.SAVE_OK:Code.SAVE_ERR,flag);
    }

    @PutMapping
    public Result update(@RequestBody Book book) {
    boolean flag = bookService.update(book);
    return new Result(flag ? Code.UPDATE_OK:Code.UPDATE_ERR,flag);
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id) {
    boolean flag = bookService.delete(id);
    return new Result(flag ? Code.DELETE_OK:Code.DELETE_ERR,flag);
    }

    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) {
    Book book = bookService.getById(id);
    Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
    String msg = book != null ? "" : "数据查询失败,请重试!";
    return new Result(code,book,msg);
    }

    @GetMapping
    public Result getAll() {
    List<Book> bookList = bookService.getAll();
    Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR;
    String msg = bookList != null ? "" : "数据查询失败,请重试!";
    return new Result(code,bookList,msg);
    }
    }

统一异常处理


异常的种类及出现异常的原因分类:

  • 框架内部 抛出的 异常:因 使用不合规 导致
  • 数据层 抛出的 异常:因 外部服务器故障 导致(例如:服务器访问超时)
  • 业务层 抛出的 异常:因 业务逻辑书写错误 导致(例如:遍历业务书写操作,导致索引异常等)
  • 表现层 抛出的 异常:因 数据收集、校验等规则 导致(例如:不匹配的数据类型间导致异常)
  • 工具类 抛出的 异常:因 工具类书写不严谨不够健壮 导致(例如:必要释放的连接长期未释放等)

注意:

  • 所有的异常均抛出到 表现层 进行处理(数据层 --> 业务层 --> 表现层)
  • 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,可以用 AOP思想 实现

异常处理器


SpringMVC提供的解决方案,集中的、统一的处理项目中出现的异常

具体格式

流程:

创建异常处理器类

名称 @RestControllerAdvice
类型 类注解
位置 Rest风格开发的控制器增强类定义上方
作用 为Rest风格开发的控制器类做增强,标明 这个类是异常处理器
名称 @ExceptionHandler
类型 方法注解
位置 专用于异常处理的控制器方法上方
作用 设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行,主要就是 用于拦截对应的异常的
1
2
3
4
5
6
7
8
9
10
11
12
// @RestControllerAdvice用于 标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {

// 除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
System.out.println("嘿嘿,异常你哪里跑!")
return new Result(666, null, "嘿嘿,异常你哪里跑!");

}
}

项目中异常处理方案


先对异常进行分类:

  • 业务异常(BusinessException)

    • 规范的 用户行为 产生的异常
      • 用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串
    • 不规范 的 用户行为 操作产生的异常
      • 如用户故意传递错误数据
  • 系统异常(SystemException)

    • 项目运行过程中 可预计但无法避免 的异常
      • 比如 数据库或服务器 宕机
  • 其他异常(Exception)

    • 编程人员 未预期到 的异常,如:用到的文件不存在

具体实现:

自定义异常类

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 自定义异常处理器,用于封装异常信息,对异常进行分类
public class SystemException extends RuntimeException{
private Integer code;

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public SystemException(Integer code, String message) {
super(message);
this.code = code;
}

public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}

}


// 自定义异常处理器,用于封装异常信息,对异常进行分类
public class BusinessException extends RuntimeException{
private Integer code;

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}

public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}

}
  • 让自定义异常类继承 RuntimeException 的好处是,后期在抛出这两个异常的时候,就不用在 try…catch… 或 throws 了
  • 自定义异常类中添加code属性的原因是为了更好的区分异常是来自哪个业务的

将 其他异常 包成 自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Book getById(Integer id) {

// 模拟业务异常,包装成自定义异常
if(id == 1){
throw new BusinessException(Code.BUSINESS_ERR,"请不要使用你的技术挑战我的耐性!");
}

// 模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
try{
int i = 1/0;
}catch (Exception e){
throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
}
return bookDao.getById(id);
}
  • 方式一:try{}catch(){} 在 catch 中重新 throw 我们自定义异常即可。
  • 方式二:直接 throw 自定义异常即可

处理器类 中 处理自定义异常

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
// @RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {

// @ExceptionHandler用于设置当前处理器类对应的异常类型
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(ex.getCode(), null, ex.getMessage());
}

@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException ex){
return new Result(ex.getCode(), null, ex.getMessage());
}

// 除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doOtherException(Exception ex){
// 记录日志
// 发送消息给运维
// 发送邮件给开发人员,ex对象发送给开发人员
return new Result(Code.SYSTEM_UNKNOW_ERR, null, "系统繁忙,请稍后再试!");
}
}

拦截器


拦截器

  • 浏览器发送一个请求会先到Tomcat的web服务器
  • Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源
  • 如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问
  • 如果是动态资源,就需要交给项目的后台代码进行处理
  • 在找到具体的方法之前,可以去配置过滤器(可以配置多个),按照顺序进行执行
  • 然后进入到到中央处理器(SpringMVC中的内容),SpringMVC会根据配置的规则进行拦截
  • 如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果
  • 如果不满足规则,则不进行处理

拦截器(Interceptor):是一种 动态拦截方法 调用的机制,在 SpringMVC 中 动态拦截控制器 方法的执行

  • 作用:
    • 在指定的方法调用前后执行预先设定的代码
    • 阻止原始方法的执行
    • 总结:拦截器就是用来做增强

拦截器 和 过滤器 之间的区别:

  • 归属 不同:Filter 属于 Servlet 技术,Interceptor 属于 SpringMVC 技术
  • 拦截内容 不同:Filter 对 所有访问 进行增强,Interceptor 仅针对 SpringMVC 的 访问 进行增强

过滤器 vs 拦截器


拦截器开发流程


  1. 创建 拦截器类

    • 让 类 实现 HandlerInterceptor 接口,重写接口中的三个方法
    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
    @Component
    // 定义拦截器类,实现HandlerInterceptor接口
    // 注意当前类必须受Spring容器控制
    public class ProjectInterceptor implements HandlerInterceptor {

    @Override
    // 原始方法调用前执行的内容
    // 这个是一定执行的
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("preHandle...");
    return true;
    }

    @Override
    // 原始方法调用后执行的内容
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("postHandle...");
    }

    @Override
    // 原始方法调用完成后执行的内容
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("afterCompletion...");
    }
    }
    • 拦截器类要被 SpringMVC 容器扫描到
    • 拦截器中的 preHandler 方法,如果返回 true,则代表放行,会执行原始 Controller 类中要请求的方法,如果返回false,则代表拦截,后面的就不会再执行了
  2. 配置 拦截器类,实现具体的拦截器配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport {

    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
    //配置拦截器
    registry.addInterceptor(projectInterceptor).addPathPatterns("/books" );
    }
    }
  3. SpringMVC 添加 SpringMvcSupport 包扫描

    1
    2
    3
    4
    5
    6
    @Configuration
    @ComponentScan({"com.itheima.controller","com.itheima.config"})
    @EnableWebMvc
    public class SpringMvcConfig{

    }
  4. 修改 拦截器 拦截规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
    //配置拦截器
    registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*" );
    }
    }
  5. 简化 SpringMvcSupport 的编写

    • 对标准接口 WebMvcConfigure 简化开发(注意:侵入式较强)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Configuration
    @ComponentScan({"com.itheima.controller"})
    @EnableWebMvc
    // 实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
    public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    // 配置多拦截器
    registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    }
    }

拦截器执行流程


拦截器参数


前置处理方法

1
2
3
4
5
6
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
  • 参数
    • request:请求对象
    • response:响应对象
    • handler:被调用的处理器对象,本质上是一个方法对象,对反射中的 Method 对象进行了再包装
  • 返回值
    • 返回值为 false,被拦截的处理器将不再执行

eg:

1
2
3
4
5
6
7
// 使用request对象可以获取请求数据中的内容,如获取请求头的 Content-Type

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String contentType = request.getHeader("Content-Type");
System.out.println("preHandle..."+contentType);
return true;
}
1
2
3
4
5
6
7
8
// 使用handler参数,可以获取方法的相关信息

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod hm = (HandlerMethod)handler;
String methodName = hm.getMethod().getName(); // 可以获取方法的名称
System.out.println("preHandle..."+ methodName);
return true;
}

后置处理方法

原始方法运行后运行,如果原始方法被拦截,则不执行

1
2
3
4
5
6
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
  • modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整

完成处理方法

拦截器最后执行的方法,无论原始方法是否执行

1
2
3
4
5
6
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion");
}
  • ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理

拦截器链的配置


当配置多个拦截器时,形成拦截器链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
// 实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {

@Autowired
private ProjectInterceptor projectInterceptor;

@Autowired
private ProjectInterceptor2 projectInterceptor2;

@Override
public void addInterceptors(InterceptorRegistry registry) {
// 配置多拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*");
}
}

拦截器执行的顺序 是和 配置顺序 有关。就和前面所提到的运维人员进入机房的案例,先进后出

  • 当配置 多个拦截器 时,形成 拦截器链
  • 拦截器链 的 运行顺序 参照 拦截器添加顺序 为准
  • 当 拦截器 中出现对 原始处理器 的拦截,后面的拦截器均终止运行
  • 当拦截器运行中断,仅运行配置在前面的拦截器的 afterCompletion 操作

拦截器的执行顺序

总结:

  • preHandle:与配置顺序相同,必定运行
  • postHandle:与配置顺序相反,可能不运行
  • afterCompletion:与配置顺序相反,可能不运行

Maven 进阶


依赖 管理


依赖指当前项目运行所需的 jar,一个项目可以设置多个依赖

格式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--设置当前项目所依赖的所有jar-->
<dependencies>

<!--设置具体的依赖-->
<dependency>

<!--依赖所属群组id-->
<groupId>org.springframework</groupId>

<!--依赖所属项目id-->
<artifactId>spring-webmvc</artifactId>

<!--依赖版本号-->
<version>5.2.10.RELEASE</version>

</dependency>

</dependencies>

依赖 具有 传递性

依赖的传递性

  • 依赖传递有 直接依赖 和 间接依赖
    • 相对于 A 来说,A 直接依赖 B 和 C ,间接依赖了D1、E1、G 以及 F、D2、E2
    • 相对于 B 来说,B 直接依赖了 D1 和 E1,间接依赖了 G
    • 直接依赖和间接依赖是一个相对的概念

依赖冲突:是指项目依赖的某一个 jar 包,有多个 不同 的版本,因而造成 类包 版本冲突

  • 特殊优先:当 同级 配置了 相同资源 的 不同版本,后配置的覆盖先配置的
  • 路径优先:当依赖中出现 相同的资源 时,层级越深,优先级越低,层级越浅,优先级越高(2度 > 3度)
  • 声明优先:当资源在 相同层级 被依赖时,配置顺序靠前的覆盖配置顺序靠后的

可选依赖:指 对外隐藏 当前所依赖 的资源(依赖不透明)

1
2
3
4
5
6
7
<dependency>
<groupId>com.itheima</groupId>
<artifactId>maven_03_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
<!--可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依赖传递-->
<optional>true</optional>
</dependency>

排除依赖:指 主动断开依赖 的资源,被排除的资源无需指定版本(依赖不需要)

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>com.itheima</groupId>
<artifactId>maven_04_dao</artifactId>
<version>1.0-SNAPSHOT</version>
<!--排除依赖是隐藏当前资源对应的依赖关系-->
<exclusions>
<exclusion>
<groupId>com.itheima</groupId>
<artifactId>maven_03_pojo</artifactId>
</exclusion>
</exclusions>
</dependency>

继承 与 聚合


聚合

聚合

聚合:将多个模块组织成一个整体,同时进行项目构建的过程称为聚合

聚合工程:通常是一个不具有业务功能的 “空” 工程(有且仅有一个 pom 文件)

作用:使用聚合工程可以将多个工程编组,通过对 聚合工程 进行构建,实现对所包含的模块进行同步构建

  • 当工程中某个模块发生更新(变更)时,必须保障工程中与已更新模块关联的模块同步更新,此时可以使用 聚合工程 来解决批量模块同步构建的问题

聚合工程主要是用来管理项目

实现流程:将项目的打包方式改为 pom,并在 pom.xml 添加所要管理的项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itheima</groupId>
<artifactId>maven_01_parent</artifactId>
<version>1.0-RELEASE</version>
<packaging>pom</packaging>

<!--设置管理的模块名称-->
<modules>
<module>../maven_02_ssm</module>
<module>../maven_03_pojo</module>
<module>../maven_04_dao</module>
</modules>
</project>

项目的打包方式,分别是

  • jar:默认情况,说明该项目为 java项目
  • war:说明该项目为 web 项目
  • pom:说明该项目为 聚合或继承 项目

继承

继承

  • 继承:描述的是两个工程间的关系(其中一个叫父工程,另外一个叫子工程),与 java 中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。
  • 作用:
    • 简化配置
    • 减少版本冲突

继承 的 实现步骤:

  • 创建 Maven 模块,设置打包类型为 pom

    1
    <packaging>pom</packaging>
  • 在 父工程 的 pom 文件中配置依赖关系(子工程将沿用父工程中的依赖关系),一般只抽取子项目中公有的 jar 包

    1
    2
    3
    4
    5
    6
    7
    8
    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency>
    ...
    </dependencies>
  • 在父工程中配置子工程中 可选 的 依赖关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
    </dependency>
    </dependencies>
    ...
    </dependencyManagement>
  • 在子工程中配置当前工程所继承的父工程

    1
    2
    3
    4
    5
    6
    7
    8
    <!--定义该工程的父工程-->
    <parent>
    <groupId>com.itheima</groupId>
    <artifactId>maven_01_parent</artifactId>
    <version>1.0-RELEASE</version>
    <!--填写父工程的pom文件,可以不写-->
    <relativePath>../maven_01_parent/pom.xml</relativePath>
    </parent>
  • 在子工程中配置使用父工程中可选依赖的坐标

    1
    2
    3
    4
    5
    6
    <dependencies>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    </dependency>
    </dependencies>

注意事项:

  1. 子工程中使用父工程中的可选依赖时,仅需要提供群组id和项目id,无需提供版本,版本由父工程统一提供,避免版本冲突
  2. 子工程中还可以定义父工程中没有定义的依赖关系,只不过不能被父工程进行版本统一管理

聚合 与 继承 的 区别

两种之间的作用:

  • 聚合用于 快速构建 项目,对项目进行管理
  • 继承用于 快速配置 和 管理子项目 中所使用 jar 包的版本

聚合和继承的 相同点:

  • 聚合与继承的 pom.xml 文件打包方式均为 pom,可以将两种关系制作到同一个pom文件中
  • 聚合与继承均属于 设计型模块,并无实际的模块内容

聚合和继承的不同点:

  • 聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
  • 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

属性


属性的使用


  1. 父工程 中 定义属性

    1
    2
    3
    4
    5
    6
    <!--定义属性-->
    <properties>
    <spring.version>5.2.10.RELEASE</spring.version>
    <junit.version>4.12</junit.version>
    <mybatis-spring.version>1.3.0</mybatis-spring.version>
    </properties>
  2. 修改依赖 的 version

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
    </dependency>

此时,只需要更新父工程中 properties 标签中所维护的 jar 包版本,所有子项目中的版本也就跟着更新。当然除了将 spring 相关版本进行维护,还可以将其他的 jar 包版本也进行抽取,这样就可以对项目中所有 jar 包的版本进行统一维护

如果项目需要有属性被 父工程 管理,如果有多个项目需要配置,解决办法:

方式一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<resources>
<!--设置资源目录,并设置能够解析${}-->
<resource>
<directory>../maven_02_ssm/src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>../maven_03_pojo/src/main/resources</directory>
<filtering>true</filtering>
</resource>
...
</resources>
</build>

可以配,但是如果项目够多的话,这个配置也是比较繁琐

方式二:

1
2
3
4
5
6
7
8
9
10
11
12
<build>
<resources>
<!--
${project.basedir}: 当前项目所在目录,子项目继承了父项目,
相当于所有的子项目都添加了资源目录的过滤
-->
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>

资源文件引用属性


步骤:

  1. 定义属性

    1
    2
    3
    4
    5
    <properties>
    <spring.version>5.2.10.RELEASE</spring.version>
    <junit.version>4.12</junit.version>
    <jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
    </properties>
  2. jdbc.properties 文件中引用属性

    1
    2
    3
    4
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=${jdbc.url}
    jdbc.username=root
    jdbc.password=root
  3. 设置 maven 过滤文件范围

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <build>
    <resources>
    <!--
    ${project.basedir}: 当前项目所在目录,子项目继承了父项目,
    相当于所有的子项目都添加了资源目录的过滤
    -->
    <resource>
    <directory>${project.basedir}/src/main/resources</directory>
    <filtering>true</filtering>
    </resource>
    </resources>
    </build>

Maven中的属性分为:

  • 自定义属性(常用)
  • 内置属性
  • Setting 属性
  • Java 系统属性
  • 环境变量属性

Maven中的属性


版本管理


版本号

  • SNAPSHOT(快照版本)
    • 项目开发过程中临时输出的版本,称为快照版本
    • 快照版本会随着开发的进展不断更新
  • RELEASE(发布版本)
    • 项目开发到一定阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构件文件是稳定的
    • 即便进行功能的后续开发,也不会改变当前发布版本内容,这种版本称为发布版本

除了上面的工程版本,还会经常能看到一些发布版本:

  • alpha版:内测版,bug多不稳定内部版本不断添加新功能
  • beta版:公测版,不稳定(比alpha稳定些),bug相对较多不断添加新功能
  • 纯数字版

多环境配置与应用


多环境配置:profile

多环境开发

流程:

  1. 父工程 配置 多个环境,并指定默认激活环境

    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
    <profiles>

    <!--开发环境-->
    <profile>
    <id>env_dep</id>
    <properties>
    <jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
    </properties>
    <!--设定是否为默认启动环境-->
    <activation>
    <activeByDefault>true</activeByDefault>
    </activation>
    </profile>


    <!--生产环境-->
    <profile>
    <id>env_pro</id>
    <properties>
    <jdbc.url>jdbc:mysql://127.2.2.2:3306/ssm_db</jdbc.url>
    </properties>
    </profile>

    <!--测试环境-->
    <profile>
    <id>env_test</id>
    <properties>
    <jdbc.url>jdbc:mysql://127.3.3.3:3306/ssm_db</jdbc.url>
    </properties>
    </profile>
    </profiles>
  2. 执行安装查看 env_dep 环境是否生效

  3. 切换默认环境为 生产环境

    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
    <profiles>

    <!--开发环境-->
    <profile>
    <id>env_dep</id>
    <properties>
    <jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
    </properties>
    </profile>

    <!--生产环境-->
    <profile>
    <id>env_pro</id>
    <properties>
    <jdbc.url>jdbc:mysql://127.2.2.2:3306/ssm_db</jdbc.url>
    </properties>
    <!--设定是否为默认启动环境-->
    <activation>
    <activeByDefault>true</activeByDefault>
    </activation>
    </profile>

    <!--测试环境-->
    <profile>
    <id>env_test</id>
    <properties>
    <jdbc.url>jdbc:mysql://127.3.3.3:3306/ssm_db</jdbc.url>
    </properties>
    </profile>
    </profiles>
  4. 执行安装并查看 env_pro 环境是否生效

  5. 再按照上面步骤测试 测试环境

对于多环境切换只需要两步即可:

  • 父工程 中定义 多环境

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <profiles>
    <profile>
    <id>环境名称</id>
    <properties>
    <key>value</key>
    </properties>
    <activation>
    <activeByDefault>true</activeByDefault>
    </activation>
    </profile>
    ...
    </profiles>
  • 使用多环境(构建过程)

    1
    2
    3
    // maven指令按钮

    mvn 指令 -P 环境定义ID[环境定义中获取]

跳过测试


方式一:IDEA工具实现跳过测试

方式一

图中的按钮为Toggle 'Skip Tests' Mode,翻译为切换的意思,也就是说在 测试与不测试 之间进行切换。说明测试已经被关闭,再次点击就会恢复。

方式二:配置插件实现跳过测试

在父工程中的 pom.xml 中添加测试插件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>false</skipTests>
<!--排除掉不参与测试的内容-->
<excludes>
<exclude>**/BookServiceTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
  • skipTests:如果为true,则跳过所有测试,如果为false,则不跳过测试
  • excludes:哪些测试类不参与测试,即排除,针对skipTests为false来设置的
  • includes: 哪些测试类要参与测试,即包含,针对skipTests为true来设置的

私服


私服

私服:公司内部搭建的用于存储Maven资源的服务器

远程仓库:Maven开发团队维护的用于存储Maven资源的服务器

也就是说,私服是一台独立的服务器,用于 解决团队内部的资源共享与资源同步 问题

搭建Maven私服的方式有很多,介绍其中一种使用量比较大的实现方式:

私服资源操作流程分析

所有私服仓库总共分为三大类:

  • 宿主仓库(hosted)

    • 保存无法从中央仓库获取的资源
      • 自主研发
      • 第三方非开源项目,比如:Oracle,因为是付费产品,所以中央仓库没有
  • 代理仓库(proxy)

    • 代理远程仓库,通过nexus访问其他公共仓库,例如中央仓库
  • 仓库组(group)

    • 将若干个仓库组成一个群组,简化配置
    • 仓库组不能保存资源,属于设计型仓库

私服仓库分类


SpringBoot


SpringBoot 概述


SpringBoot 是由 Pivotal 团队提供的全新框架,其设计目的是用来 简化 Spring 应用的 初始搭建 以及 开发过程

SpringBoot的官网:https://spring.io/projects/spring-boot

Spring程序与SpringBoot程序对比

  • 坐标

    Spring 程序中的坐标需要自己编写,而且坐标非常多

    SpringBoot 程序中的坐标是在创建工程时进行勾选自动生成的

  • web3.0配置类

    Spring 程序需要自己编写这个配置类。

    SpringBoot 程序不需要自己书写

  • 配置类

    Spring/SpringMVC 程序的配置类需要自己书写。而 SpringBoot 程序则不需要书写。

注意:基于 Idea 的 Spring Initializr 快速构建 SpringBoot 工程时需要联网

SpringBoot 工程 的 打包:通过 Maven 的 package 指令打包就会在 target 目录下生成对应的 jar 包,同时在构建 SpringBoot 工程时已经在 pom.xml 中配置了如下插件:

1
2
3
4
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

SpringBoot 工程 的 启动:进入 jar 包所在位置,在 命令提示符 中输入如下命令:

1
java -jar xxx.jar

SpringBoot 程序优点 恰巧就是针对 Spring 的缺点

  • 自动配置(简化依赖配置)
    • 这个是用来解决 Spring 程序配置繁琐的问题
  • 起步依赖
    • 这个是用来解决 Spring 程序依赖设置繁琐的问题
  • 辅助功能(内置服务器, …)
    • 在启动 SpringBoot 程序时既没有使用本地的 tomcat 也没有使用 tomcat 插件,而是使用 SpringBoot 内置的服务器

首先看 SpringBoot 的 pom.xml 依赖:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<!--指定了一个父工程,父工程中的东西在该工程中可以继承过来使用-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>springboot_01_quickstart</artifactId>
<version>0.0.1-SNAPSHOT</version>

<!--JDK 的版本-->
<properties>
<java.version>8</java.version>
</properties>

<dependencies>

<!--该依赖就是我们在创建 SpringBoot 工程勾选的那个 Spring Web 产生的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--这个是单元测试的依赖,我们现在没有进行单元测试,所以这个依赖现在可以没有-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<!--这个插件是在打包时需要的,而这里暂时还没有用到-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

起步依赖(starter):SpringBoot 中常见项目名称,定义了当前项目使用的所有项目坐标,以达到减少依赖配置的目的,以后需要用到什么技术,直接导入对应的 起步依赖 即可,里面用到的各种依赖的版本号都已经配置好了。

spring-boot-starter-parent(parent)

  • 所有 SpringBoot 项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到 减少依赖冲突 的目的
  • spring-boot-starter-parent(2.5.0)与 spring-boot-starter-parent(2.4.6)共计57处坐标版本不同

实际开发

  • 使用任意坐标时,仅书写 GAV 中的 G 和 A ,V 由 SpringBoot 提供

    G:groupid

    A:artifactId

    V:version

  • 如发生坐标错误,再指定 version(要小心版本冲突)

程序启动:

创建的每一个 SpringBoot 程序时都包含一个类似于下面的类,将这个类称作 引导类

1
2
3
4
5
6
7
@SpringBootApplication
public class Springboot01QuickstartApplication {

public static void main(String[] args) {
SpringApplication.run(Springboot01QuickstartApplication.class, args);
}
}

注意:

  • SpringBoot 在创建项目时,采用 jar 的打包方式

  • SpringBoot 的 引导类 是项目的入口,运行 main 方法就可以启动项目

    • 因为我们在 pom.xml 中配置了 spring-boot-starter-web 依赖,而该依赖是依赖 tomcat ,所以运行 main 方法就可以使用 tomcat 启动工程

现在启动工程使用的是 tomcat 服务器,那能不能不使用 tomcat 而使用 jetty 服务器,需要导入下面的starter 依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
  • Jetty 比 Tomcat 更轻量级,可扩展性更强(相较于Tomcat),谷歌应用引擎(GAE)已经全面切换为Jetty

基础配置


配置文件格式


SpringBoot 提供了多种属性 配置方式

  • application.properties

    1
    server.port=80
  • application.yml

    1
    2
    server:
    port: 81
  • application.yaml

    1
    2
    server:
    port: 82

注意:SpringBoot 程序的配置文件名必须是 application ,只是 后缀名不同 而已

启动服务,在控制台可以看到使用的端口号是 80。说明 application.properties 的优先级最高

注释掉 application.properties 配置文件内容。再次启动服务,在控制台可以看到使用的端口号是 81,说明 application.yml 配置文件为第二优先级。

从上述的验证结果可以确定 三种配置文件 的 优先级 是:

application.properties > application.yml > application.yaml

注意:

  • SpringBoot 核心配置文件名为 application

  • SpringBoot 内置属性过多,且所有属性集中在一起修改,在使用时,通过提示键 + 关键字修改属性

    例如:要设置日志的级别时,可以在配置文件中书写 logging,就会提示出来。配置内容如下

    1
    2
    3
    logging:
    level:
    root: info

yaml 格式


**YAML(YAML Ain’t Markup Language),一种数据序列化格式。**这种格式的配置文件在近些年已经占有主导地位,那么这种配置文件和前期使用的配置文件是有一些优势的,下面是一些数据格式的比对:

最开始我们使用的是 xml ,格式如下:

1
2
3
4
5
<enterprise>
<name>itcast</name>
<age>16</age>
<tel>4006184000</tel>
</enterprise>

而 properties 类型的配置文件如下:

1
2
3
enterprise.name=itcast
enterprise.age=16
enterprise.tel=4006184000

yaml 类型的配置文件内容如下:

1
2
3
4
enterprise:
name: itcast
age: 16
tel: 4006184000

优点:

  • 容易阅读

    yaml 类型的配置文件比 xml 类型的配置文件更容易阅读,结构更加清晰

  • 容易 与脚本语言交互

  • 以 数据 为核心,重数 据轻格式

    yaml 更注重数据,而 xml 更注重格式

YAML 文件扩展名:

  • .yml (主流)
  • .yaml

上面两种后缀名都可以,以后使用更多的还是 yml 的

yml的语法格式:

  • 大小写 敏感

  • 属性层级关系使用多行描述,每行结尾使用 冒号 结束

  • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键),空格的个数并不重要,只要保证同层级的左侧对齐即可

  • 属性值前面添加空格(属性名与属性值之间使用 冒号 + 空格 作为分隔)

  • # 表示 注释

核心规则:数据前面要加 空格 与 冒号 隔开

数组数据在数据书写位置的下方使用 减号 作为数据开始符号,每行书写一个数据,减号与数据间空格分隔,例如:

1
2
3
4
5
6
7
8
9
enterprise:
name: itcast
age: 16
tel: 4006184000
subject:
# 下面是数组格式
- Java
- 前端
- 大数据

yaml 数据读取


@Value(直接读取)

使用 @Value("表达式") 注解可以从 配合文件 中读取数据

注解中用于读取属性名引用方式是:${一级属性名.二级属性名……}

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
@RequestMapping("/books")
public class BookController {

@Value("${lesson}")
private String lesson;
@Value("${server.port}")
private Integer port;
@Value("${enterprise.subject[0]}")
private String subject_00;

@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println(lesson);
System.out.println(port);
System.out.println(subject_00);
return "hello , spring boot!";
}
}

Environment(封装后读取)

上面方式读取到的数据特别零散,SpringBoot 还可以使用 @Autowired 注解注入 Environment 对象的方式读取数据

这种方式 SpringBoot 会将配置文件中所有的数据封装到 Environment 对象中,如果需要使用哪个数据只需要通过调用 Environment 对象的 getProperty(String name) 方法获取

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping("/books")
public class BookController {

@Autowired
private Environment env;

@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println(env.getProperty("lesson"));
System.out.println(env.getProperty("enterprise.name"));
System.out.println(env.getProperty("enterprise.subject[0]"));
return "hello , spring boot!";
}
}

注意:这种方式,框架内容大量数据,而在 开发中 很少使用


实体类封装属性(封装后读取)

SpringBoot 还提供了将配置文件中的数据封装到 自定义的实体类对象 中的方式。具体操作如下:

  • 将实体类 bean 的创建交给 Spring 管理。

    • 在类上添加 @Component 注解
  • 使用 @ConfigurationProperties 注解表示加载配置文件

    • 在该注解中也可以使用 prefix 属性指定只加载指定前缀的数据
  • 在 BookController 中进行注入

eg:

Enterprise 实体类内容如下:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Component
@ConfigurationProperties(prefix = "enterprise")
public class Enterprise {
private String name;
private int age;
private String tel;
private String[] subject;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getTel() {
return tel;
}

public void setTel(String tel) {
this.tel = tel;
}

public String[] getSubject() {
return subject;
}

public void setSubject(String[] subject) {
this.subject = subject;
}

@Override
public String toString() {
return "Enterprise{" +
"name='" + name + '\'' +
", age=" + age +
", tel='" + tel + '\'' +
", subject=" + Arrays.toString(subject) +
'}';
}
}

BookController 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/books")
public class BookController {

@Autowired
private Enterprise enterprise;

@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println(enterprise.getName());
System.out.println(enterprise.getAge());
System.out.println(enterprise.getSubject());
System.out.println(enterprise.getTel());
System.out.println(enterprise.getSubject()[0]);
return "hello , spring boot!";
}
}

这种方式会 报警告,解决办法:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

多环境配置


application.yml 配置文件内容如下:

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
# 设置启用的环境
spring:
profiles:
active: dev

--- # 这里 --- 用于区分不同的生产环境
# 开发环境
spring:
profiles: dev
# 设置开发环境具体参数设定
server:
port: 80
---
# 生产环境
spring:
profiles: pro
# 设置生产环境具体参数设定
server:
port: 81
---
# 测试环境
spring:
profiles: test
# 设置测试环境具体参数设定
server:
port: 82
---

最新的写法:

1
2
3
4
5
# 开发环境
spring:
config:
activate:
on-profile: dev

SpringBoot 定义了配置文件不同的放置的位置,而放在不同位置的优先级时不同的。

SpringBoot 中4级配置文件放置位置:

  • 1级:classpath(类路径下):application.yml (最低)
  • 2级:classpath:config/application.yml
  • 3级:file (文件路径下):application.yml
  • 4级:file :config/application.yml (最高)

说明:

  • 级别越高 优先级越高
  • 1级 与 2级 用作系统开发阶段设置通用属性
  • 3级 与 4级 留作系统打包后设置通用属性

SpringBoot 整合 junit


对比 Spring 整合 junit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith(SpringJUnit4ClassRunner.class)  // 设置运行器
@ContextConfiguration(classes = SpringConfig.class) // 加载环境
public class UserServiceTest {

// 注入测试对象
@Autowired
private BookService bookService;

// 测试功能
@Test
public void testSave(){
bookService.save();
}
}

使用 @RunWith 注解指定运行器,使用 @ContextConfiguration 注解来指定配置类或者配置文件

而 SpringBoot 整合 junit 特别简单,分为以下三步完成:

  • 在测试类上添加 SpringBootTest 注解
  • 使用 @Autowired 注入要测试的资源
  • 定义测试方法进行测试

在 test/java 下创建 com.itheima 包,在该包下创建测试类,将 BookService 注入到该测试类中

eg:

1
2
3
4
5
6
7
8
9
10
11
@SpringBootTest
class Springboot07TestApplicationTests {

@Autowired
private BookService bookService;

@Test
public void save() {
bookService.save();
}
}

注意:这里的引导类所在包 必须是 测试类所在包 及其 子包。

例如:

  • 引导类所在包是 com.itheima
  • 测试类所在包是 com.itheima

如果不满足这个要求的话,就需要在使用 @SpringBootTest 注解时,使用 classes 属性指定引导类的字节码对象。如 @SpringBootTest(classes = Springboot07TestApplication.class)


SpringBoot 整合 Mybatis


对比 Spring 整合 Mybatis 需要定义很多配置类

  • SpringConfig 配置类

    • 导入 JdbcConfig 配置类

    • 导入 MybatisConfig 配置类

      1
      2
      3
      4
      5
      6
      7
      @Configuration
      @ComponentScan("com.itheima")
      @PropertySource("classpath:jdbc.properties")
      @Import({JdbcConfig.class,MyBatisConfig.class})
      public class SpringConfig {
      }

  • JdbcConfig 配置类

    • 定义数据源(加载 properties 配置项:driver、url、username、password)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class JdbcConfig {
      @Value("${jdbc.driver}")
      private String driver;
      @Value("${jdbc.url}")
      private String url;
      @Value("${jdbc.username}")
      private String userName;
      @Value("${jdbc.password}")
      private String password;

      @Bean
      public DataSource getDataSource(){
      DruidDataSource ds = new DruidDataSource();
      ds.setDriverClassName(driver);
      ds.setUrl(url);
      ds.setUsername(userName);
      ds.setPassword(password);
      return ds;
      }
      }
  • MybatisConfig 配置类

    • 定义 SqlSessionFactoryBean

    • 定义映射配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      @Bean
      public MapperScannerConfigurer getMapperScannerConfigurer(){
      MapperScannerConfigurer msc = new MapperScannerConfigurer();
      msc.setBasePackage("com.itheima.dao");
      return msc;
      }

      @Bean
      public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource){
      SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
      ssfb.setTypeAliasesPackage("com.itheima.domain");
      ssfb.setDataSource(dataSource);
      return ssfb;
      }

而 SpringBoot 整合 Mybatis 的流程:

  1. 定义实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Book {
    private Integer id;
    private String name;
    private String type;
    private String description;

    //setter and getter

    //toString
    }
  2. 定义dao接口

    1
    2
    3
    4
    5
    @Mapper
    public interface BookDao {
    @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id);
    }
  3. 使用 Druid 数据源

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
    </dependency>
  4. 在 application.yml 配置文件配置

    1
    2
    3
    4
    5
    6
    7
    spring:
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    • 注意:SpringBoot 版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在 url 连接串中配置时区 jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC,或在MySQL数据库端配置时区解决此问题