SSM框架与SpringBoot

spring框架概述

1、spring是轻量级的开源JavaEE框架

2、spring可以解决企业应用开发的复杂性

3、spring有两个核心部分:Ioc和Aop

  • Ioc:控制反转,把创建对象过程交给spring进行管理
  • Aop:面向切面,不修改源代码进行功能增强

4、spring特点

  • 方便解耦,简化开发
  • Aop编程支持
  • 方便程序测试
  • 方便和其他框架进行整合
  • 方便进行事务操作
  • 降低API开发难度

Spring Framework系统架构 

  • Data Access:数据访问
  • Data Intergration:数据集成
  • Web:Web开发
  • AOP:面向切面编程
  • Aspects:AOP思想
  • Core Container:核心容器
  • Test:单元测试与集成测试 

核心概念

  • IoC(Inversion of Control)控制反转
    • 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想成为控制反转
  • Spring技术对Ioc思想进行了实现
    • Spring提供了一个容器,称为Ioc容器,用来充当Ioc思想中的“外部”
    • Ioc是容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在Ioc容器中统称为Bean
  • DI(Dependency Injection)依赖注入
    • 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入

Ioc入门案例

思路分析:

  • 1.管理什么?(service与dao)
  • 2.如何将被管理的对象告知Ioc容器?(配置)
  • 3.被管理的对象交给Ioc容器,如何获取到Ioc容器?(接口)
  • 4.Ioc容器得到后,如何从容器中获取bean(接口方法)

1、导入spring坐标

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

2、定义spring管理的类(接口)

public interface BookService {
    void save();
}
public class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookDaoImpl();
    public void save(){
        System.out.println("book service save");
        bookDao.save();
    }
}

3、创建spring配置文件,配置对应类作为spring管理的bean

<?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"/>
</beans>

bean定义时id属性在同一个上下文中不能重复

4、初始化Ioc容器(spring核心容器/spring是容器),通过容器获取bean

public class App {
    public static void main(String[] args) {
        //3.获取Ioc容器
        ApplicationContext ctx  = new ClassPathXmlApplicationContext("applicationContext.xml");

        //4.获取bean
        /*BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();*/
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

DI入门案例

思路分析:

  • 1.基于Ioc管理bean
  • 2.service中使用new形式创建的dao对象是否保留(否)
  • 3.service中需要的dao对象如何进入到service中?(提供方法)
  • 4.service与dao间的关系如何描述?(配置)

1、删除使用new的形式创建对象的方法

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

2、提供依赖对象对应的setter方法

public class BookServiceImpl implements BookService {
    //5.删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
    public void save(){
        System.out.println("book service save");
        bookDao.save();
    }
    //6.提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

3、配置service与dao之间的关系

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

bean基础配置

bean的别名

类别描述
名称name
类型属性
所属bean标签
功能定义bean的别名,可定义多个,使用逗号(,)分号(:)空格( )分隔
范例
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" name="service service2 bookEbi" class="com.itheima.service.impl.BookServiceImpl">
        <!--7.配置server与dao的关系-->
        <!--property标签表示配置当前bean的属性
        name属性表示配置哪一个具体的属性
        ref属性表示参照哪一个bean-->
        <property name="bookDao" ref="bookDao"/>
</bean>

获取bean无论通过id还是name获取,如果无法获取到,将抛出异常NoSuchBeanDefinitionException: No bean named 'bookEbi1' available

bean作用范围配置

类别描述
名称scope
类型属性
所属bean
功能

定义bean的作用范围,可选范围如下

singleton:单例(默认)

prototype:非单例

范例
<bean id="bookService" name="service service2 bookEbi" scope="prototype" class="com.itheima.service.impl.BookServiceImpl"/>
    
  • 为什么bean默认为单例?
  • 适合交给容器进行管理的bean
    • 表现层对象
    • 业务层对象
    • 数据层对象
    • 工具对象
  • 不适合交给容器进行管理的bean
    • 封装实体的域对象

bean的实例化

实例化bean的三种方法--构造方法(常用)

  • 提供可访问的构造方法
public class BookDaoImpl implements BookDao{
    public BookDaoImpl(){
        System.out.println("book constructor is running");
    }
    public void save(){
        System.out.println("book dao save ...");
    }
}
  • 配置
 <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
  • 无参构造方法如果不存在,将抛出BeanCreationException

 实例化bean的三种方法--静态工厂(了解)

  • 静态工厂
public class OrderDaoFactory{
    public static OrderDao getOrderDao(){
        return new OrderDaoImpl;
    }
}
  •  配置
 <bean id="orderDao" factory-method="getOrderDao" class="com.itheima.factory.OrderDaoFactory"/>

实例化bean的三种方法--实例工厂(了解)

  • 实例工厂
public class UserDaoFactory{
    public  UserDao getUserDao(){
        return new UserDaoImpl;
    }
}
  •  配置
<bean id="userDaoFactory" class="com.itheima.factory.userDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userDaoFactory"/>

实例化bean的第四种方法--FactoryBean

public class UserDaoFactoryBean implement FactoryBean<UserDao>{
    public  UserDao getObject() throws Exception{
        return new UserDaoImpl;
    }
    public Class<?> getObjectType(){
        return UserDao.class;
    }
}
  • 配置
<bean id="userDaoFactory" class="com.itheima.factory.userDaoFactoryBean"/>

bean生命周期

  • bean生命周期:bean从创建到销毁的整体过程
    • 初始化容器
      • 创建对象(内存分配)
      • 执行构造方法
      • 执行属性注入(set方法)
      • 执行bean初始化方法
    • 使用bean
      • 执行业务操作
    • 关闭/销毁容器
      • 执行bean销毁方法
  • bean生命周期控制:在bean创建后到销毁前做的一些事情
  • bean销毁时机
    • 容器关闭前触发bean的销毁
    • 关闭bean方式
      • 手工关闭容器
        • ConfigurableApplicationContext接口close()操作
      • 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
        • ConfigurableApplicationContext接口registerShutdownHook()操作
public class AppForLifecycle {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ctx.close();
        //ctx.registerShutdownHook()
    }
}

bean生命周期控制

  • 提供生命周期控制方法
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
    public void init() {
        System.out.println("book init ...");
    }
    public void destroy() {
        System.out.println("book destroy ...");
    }
}
  • 配置生命周期控制方法
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
  • 实现InitializingBean,DisposableBean接口
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {

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

依赖注入方式

  • 向一个类中传递数据的方式有几种?1.普通方法(set方法)2.构造方法
  • 依赖注入描述了在容器中建立bean与bean之前依赖关系的过程,如果bean运行需要的是数字或者字符串呢?1.引用类型2.简单类型(基本数据类型与String)
  • 依赖注入方式
    • setter注入
      • 简单类型
      • 引用类型(见DI入门案例)
    • 构造器注入
      • 简单类型
      • 引用类型

setter注入--简单类型

在bean中定义简单类型属性

public class BookDaoImpl implements BookDao {
    private int connectionNum;
    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }
}

配置中使用property标签value属性注入简单类型数据

<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl">
        <property name="connectionNum" value="100"/>
</bean>

构造器注入--引用类型(了解)

在bean中定义引用类型属性并提供可访问的构造方法

public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    public BookServiceImpl(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

配置中使用constructor-arg标签ref属性注入引用类型对象

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

构造器注入--简单类型(了解)

在bean中定义简单类型属性并提供可访问的构造方法

public class BookDaoImpl implements BookDao {
    private int connectionNum;
    public BookDaoImpl(int connectionNum) {
        this.connectionNum = connectionNum;
    }
}

配置中使用constructor-arg标签value属性注入简单类型对象

<bean id="bookDao"class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg name="connectionNum" value="100"/>
</bean>

构造器注入--参数适配(了解)

配置中使用constructor-arg标签type属性设置按形参类型注入

<bean id="bookDao"  class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg type="java.lang.String" value="mysql"/>
        <constructor-arg type="int" value="100"/>
</bean>

配置中使用constructor-arg标签index属性设置按形参位置注入

<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg index="1" value="mysql"/>
        <constructor-arg index="0" value="100"/>
</bean>

依赖注入方式选择

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
  2. 可选依赖使用setter注入进行,灵活性强
  3. spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入

依赖自动装配 

配置中使用bean标签autowire属性设置自动装配的类型

<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl"></bean>
<bean id="bookService" name="service service2 bookEbi" scope="prototype" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>

依赖自动装配特征

  • 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  • 使用按类型装配时(bytype)

注入集合对象

注入数组对象

<property name="array">
    <array>
        <value>100</value>
        <value>200</value>
        <value>300</value>
    </array>
</property>

注入List对象(重点)

<property name="list">
    <list>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>chuanzhihui</value>
    </list>
</property>

 注入Set对象

<property name="set">
    <set>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
    </set>
</property>

 注入Map对象(重点)

<property name="map">
    <map>
        <entry key="country" value="china"/>
        <entry key="province" value="henan"/>
        <entry key="city" value="kaifeng"/>
    </map>
</property>

  注入Properties对象

<property name="properties">
    <props>
        <prop key="country">china</prop>
        <prop key="province">henan</prop>
        <prop key="city">kaifeng</prop>
    </props>
</property>

数据源对象管理

导入druid坐标

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

配置数据源对象作为spring管理的bean

<bean  id="dataSource" 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>

加载properties文件

开启context命名空间

<?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/>

使用context命名空间,加载指定properties文件

<context:property-placeholder location="jdbc.properties"/>

使用${}读取加载的属性值

<property name="name" value="${jdbc.username}"/>

不加载系统属性

<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>

加载多个properties文件

<context:property-placeholder location="jdbc.properties,jdbc2.properties"/>

加载所有properties文件

<context:property-placeholder location="*.properties"/>

加载properties文件标准格式

<context:property-placeholder location="classpath:*.properties"/>

从类路径或jar包中搜索并加载properties文件

<context:property-placeholder location="classpath*:*.properties"/>

容器

创建容器

方式一:类路径加载配置文件

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

方式二:文件路径加载配置文件

ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\SSM\\src\\main\\resources\\applicationContext.xml");

加载多个配置文件

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

获取bean

方式一:使用bean名称获取

BookDao bookDao = (BookDao) ctx.getBean("bookDao");

方式二:使用bean名称获取并指定类型

BookDao bookDao = ctx.getBean("bookDao",BookDao.class);

方式三:使用bean类型获取

BookDao bookDao = ctx.getBean(BookDao.class);

容器类层次结构图

beanfactory初始化

类路径加载配置文件

Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean(BookDao.class);
bookDao.save();

beanfactory创建完毕后,所有的bean均为延迟加载

注解开发

注解开发定义bean

使用@Component定义bean

@Component("bookDao")
public class BookDaoImpl implements BookDao {
}
@Component
public class BookServiceImpl implements BookService {
}

 核心配置文件中通过组件扫描加载bean

<context:component-scan base-package="com.itheima"/>

spring提供@component注解的三个衍生注解

  • @Controller:用于表现层bean定义
  • @Service:用于业务层bean定义
  • @Repository:用于数据层bean定义
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
}
@Service
public class BookServiceImpl implements BookService {
}

纯注解开发

spring3.0开启了纯注解开发模式,使用Java类替代配置文件,开启了spring快速开发赛道

Java类代替spring核心配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.itheima"/>

</beans>
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}

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

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

@ComponentScan({"com.itheima.service","com.itheima.dao"})

读取spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象

public class AppForAnnotation {
    public static void main(String[] args) {
        //ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //AnnotationConfigApplicationContext加载Spring配置类初始化Spring容器
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao);
        //按类型获取bean
        BookService bookService = ctx.getBean(BookService.class);
        System.out.println(bookService);
    }
}

bean的管理

bean的作用范围

使用@scope定义bean作用范围

@Repository
//@Scope设置bean的作用范围
@Scope("singleton")
public class BookDaoImpl implements BookDao {
}

bean生命周期

使用@PostConstruct、@PreDestroy定义bean生命周期

@Repository
@Scope("singleton")
public class BookDaoImpl implements BookDao {

    public void save() {
        System.out.println("book dao save ...");
    }
    //@PostConstruct设置bean的初始化方法
    @PostConstruct
    public void init() {
        System.out.println("init ...");
    }
    //@PreDestroy设置bean的销毁方法
    @PreDestroy
    public void destroy() {
        System.out.println("destroy ...");
    }
}

依赖注入

引用类型:使用@Autowired注解开启自动装配模式(按类型)

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}
  • 注意:自动装配基于反射设计创建对象并暴力反射为对应属性为私有属性初始化数据,因此无需提供setter方法
  • 注意:自动装配建议使用无参构造方法创建对象(默认),如不提供对应构造方法,请提供唯一的构造方法

使用@Qualifier注解开启指定名称装配bean

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    //@Qualifier:自动装配bean时按bean名称装配
    @Qualifier("bookDao")
    private BookDao bookDao;
}
  • 注意:@Qualifier注解无法单独使用,必须配合@Autowired注解使用

 简单类型:使用@Value实现简单类型注入

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    //@Value:注入简单类型(无需提供set方法)
    @Value("${name}")
    private String name;

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

配置文件  

name=itheima888

加载properties文件

使用@PropertySource注解加载properties文件

@Configuration
@ComponentScan("com.itheima")
//@PropertySource加载properties配置文件
@PropertySource({"jdbc.properties"})
public class SpringConfig {
}

注意:路径仅仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符*

第三方bean管理

使用@Bean配置第三方Bean

//2.添加@Bean,表示当前方法的返回值是一个bean
@Bean
public DataSource dataSource(){
    DruidDataSource ds = new DruidDataSource();
    ds.setDriverClassName(driver);
    ds.setUrl(url);
    ds.setUsername(userName);
    ds.setPassword(password);
    return ds;
}

将独立的配置类加入核心配置

方式一:导入式

public class JdbcConfig {
    @Bean
    public DataSource dataSource(){
        //相关配置
        return ds;
    }
}

 使用@Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式

@Configuration
//@Import:导入配置信息
@Import({JdbcConfig.class})
public class SpringConfig {
}

方式二:扫描式

@Configuration
public class JdbcConfig {
    @Bean
    public DataSource dataSource(){
        //相关配置
        return ds;
    }
}

 使用@ComponentScan注解扫描配置类所在的包,加载对应的配置类信息

@Configuration
@ComponentScan({"com.itheima.config","com.itheima.service","com.itheima.dao"})
public class SpringConfig {
}

第三发bean依赖注入

简单类型注入

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("root")
    private String password;
    //2.添加@Bean,表示当前方法的返回值是一个bean
    //@Bean修饰的方法,形参根据类型自动装配
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}

引用类型依赖注入

public class JdbcConfig {
    @Bean
    public DataSource dataSource(BookDao bookDao){
        System.out.println(bookDao);
        //属性设置
        return ds;
    }
}

引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象

Spring整合MyBatis

导入依赖

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

  <!--Spring操作与数据库相关的-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.10.RELEASE</version>
  </dependency>
  
  <!--Spring整合MyBatis-->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.0</version>
  </dependency>

</dependencies>

 jdbc.properties

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

 JdbcConfig 

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;
    }
}

 MybatisConfig 

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;
    }
}

 SpringConfig 

@Configuration
@ComponentScan("com.itheima")
//@PropertySource:加载类路径jdbc.properties文件
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}

 实体类:Account

public class Account implements Serializable {

    private Integer id;
    private String name;
    private Double money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

数据访问对象:AccountDao  

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);
}

服务层:AccountServiceImpl 

@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();
    }
}

Spring整合JUnit

导入依赖

<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>
//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
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());
    }
}

AOP简介

  • AOP(Aspect Orient Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
    • OOP(Object Orient Programming)面向对象编程
  • 作用:在不惊动原始设计的基础上为其进行功能增强
  • Spring理念:无入侵式/无侵入式

AOP核心概念

  • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
    • 在SpringAOP中,理解为方法的执行
  • 切入点(Pointcut):匹配连接点的式子
    • 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法
      • 一个具体的方法:如com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
    • 连接点范围要比切入点范围大,是切入点的方法也一定是连接点,但是是连接点的方法就不一定要被增强,所以可能不是切入点。

  • 通知(Advice):在切入点处执行的操作,也就是共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类
  • 切面(Aspect):描述通知与切入点的对应关系。

AOP入门案例

需求分析:

在方法执行前输出当前系统时间。

思路分析:

1.导入坐标(pom.xml)

2.制作连接点(原始操作,Dao接口与实现类)

3.制作共性功能(通知类与通知)

4.定义切入点

5.绑定切入点与通知关系(切面)

AOP实现步骤

步骤1:添加依赖

<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:定义接口与实现类 

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:定义通知类和通知

通知就是将共性功能抽取出来后形成的方法,该案例的共性功能指的就是当前系统时间的打印。 

public class MyAdvice {
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

类名和方法名没有要求,可以任意。

步骤4:定义切入点

public class MyAdvice {
    设置切入点,要求配置在方法上方
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}
  • 切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。

步骤5:制作切面

切面是用来描述通知和切入点之间的关系

public class MyAdvice {
    //设置切入点,要求配置在方法上方
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    //设置在切入点pt()的前面运行当前操作(前置通知)
     @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行==位置==

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

//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
    //设置切入点,要求配置在方法上方
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    //设置在切入点pt()的前面运行当前操作(前置通知)
     @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

步骤7:开启注解格式AOP功能

@Configuration
@ComponentScan("com.itheima")
//开启注解开发AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {
}

步骤8:运行程序

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.update();
}

运行结果: 

执行update方法之前打印了系统时间戳,说明对原始方法进行了增强,AOP编程成功

AOP工作流程

流程1:Spring容器启动

  • 容器启动就需要去加载bean,哪些类需要被加载呢?
  • 需要被增强的类,如:BookServiceImpl
  • 通知类,如:MyAdvice
  • 注意此时bean对象还没有创建成功

流程2:读取所有切面配置中的切入点

  • 上面这个例子中有两个切入点的配置,但是第一个ptx()并没有被使用,所以不会被读取。

流程3:初始化bean

判定bean对应的类中的方法是否匹配到任意切入点

  • 注意第1步在容器启动的时候,bean对象还没有被创建成功。

  • 要被实例化bean对象的类中的方法和切入点进行匹配

  • 匹配失败,创建原始对象,如UserDao
    • 匹配失败说明不需要增强,直接调用原始对象的方法即可。
  • 匹配成功,创建原始对象(目标对象)的代理对象,如:BookDao
    • 匹配成功说明需要对其进行增强
    • 对哪个类做增强,这个类对应的对象就叫做目标对象
    • 因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其创建一个代理对象
    • 最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强

流程4:获取bean执行方法

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

AOP切入点表达式

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

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

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

@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
    //设置切入点,要求配置在方法上方
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    /*@Pointcut("execution(void com.itheima.dao.impl.BookDaoImpl.update())")*/
    private void pt(){}

    //设置在切入点pt()的前面运行当前操作(前置通知)
     @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}
  • 切入点表达式标准格式:动作关键字(访问修饰符(可以省略) 返回值 包名.类/接口名.方法名(参数) 异常名(可以省略))

通配符

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

    execution(public * com.itheima.*.UserService.find*(*))
    

    匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

  • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

    execution(public User com..UserService.findById(..))
    

    匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

  • +:专用于匹配子类类型

    execution(* *..*Service+.*(..))
    

    这个使用率较低。*Service+,表示所有以Service结尾的接口的子类。

书写技巧

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

AOP通知类型

  • 前置通知
  • 后置通知
  • 环绕通知(重点)
  • 返回后通知(了解)
  • 抛出异常后通知(了解)

(1)前置通知,追加功能到方法执行前,类似于在代码1或者代码2添加内容

(2)后置通知,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容

(3)返回后通知,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加

(4)抛出异常后通知,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加

(5)环绕通知,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能。

前置通知 

@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 ...");
    }
}

后置通知

@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 ...");
    }
}

环绕通知

  1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
  2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
  3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为Object类型
  4. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常

环绕通知需要在原始方法的前后进行增强,所以环绕通知就必须要能对原始操作进行调用

@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 ...");
    }
}

如果我们使用环绕通知的话,要根据原始方法的返回值来设置环绕通知的返回值

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    
    @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();
        System.out.println("around after advice ...");
        return ret;
    }
}
  • 返回的是Object而不是int的主要原因是Object类型更通用。
  • ​在环绕通知中是可以对原始方法返回值就行修改的。

业务层接口执行效率

  • 需求:任意业务层接口执行均可显示其执行效率(执行时长)

具体实现的思路:

(1) 开始执行方法之前记录一个时间

(2) 执行方法

(3) 执行完方法之后记录一个时间

(4) 用后一个时间减去前一个时间的差值,就是我们需要的结果。

所以要在方法执行的前后添加业务,经过分析我们将采用环绕通知

核心业务,记录万次执行的时间

@Component
@Aspect
public class ProjectAdvice {
    //配置业务层的所有方法
    @Pointcut("execution(* com.itheima.service.*Service.*(..))")
    private void servicePt(){}
    //@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式
    @Around("servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp){
        //获取执行签名信息
        Signature signature = pjp.getSignature();
        //通过签名获取执行操作名称(接口名)
        String className = signature.getDeclaringTypeName();
        //通过签名获取执行操作名称(方法名)
        String methodName = signature.getName();
        
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
           pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
    } 
}

AOP通知获取数据

  • 获取切入点方法的参数,所有的通知类型都可以获取参数
    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    • ProceedingJoinPoint:适用于环绕通知
  • 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
    • 抛出异常后通知
    • 环绕通知

获取参数

非环绕通知获取方式

在方法上添加JoinPoint,通过JoinPoint来获取参数

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @Before("pt()")
    public void before(JoinPoint jp) 
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice ..." );
    }
	//...其他的略
}

环绕通知获取方式

环绕通知使用的是ProceedingJoinPoint,因为ProceedingJoinPoint是JoinPoint类的子类,所以对于ProceedingJoinPoint类中应该也会有对应的getArgs()方法

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @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;
    }
}

当需要修改原始方法的参数时,就只能采用带有参数的方法,如下:

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @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;
    }
}
百度网盘密码数据兼容处理 

需求: 对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理。

@Component
@Aspect
public class DataAdvice {
    @Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
    private void servicePt(){}
    
    @Around("DataAdvice.servicePt()")
    // @Around("servicePt()")这两种写法都对
    public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
        //获取原始方法的参数
        Object[] args = pjp.getArgs();
        for (int i = 0; i < args.length; i++) {
            //判断参数是不是字符串
            if(args[i].getClass().equals(String.class)){
                args[i] = args[i].toString().trim();
            }
        }
        //将修改后的参数传入到原始方法的执行中
        Object ret = pjp.proceed(args);
        return ret;
    }
    
}

有了这个特性后,我们就可以在环绕通知中对原始方法的参数进行拦截过滤,避免由于参数的问题导致程序无法正确运行,保证代码的健壮性。

获取返回值 

环绕通知获取返回值

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @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;
    }
}

返回后通知获取返回值

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @AfterReturning(value = "pt()",returning = "ret")
    public void afterReturning(Object ret) {
        System.out.println("afterReturning advice ..."+ret);
    }
}

注意:

  • 参数名的问题

  • afterReturning方法参数类型的问题
    • 参数类型可以写成String,但是为了能匹配更多的参数类型,建议写成Object类型
  • afterReturning方法参数的顺序问题

获取异常 

环绕通知获取异常

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;
        Object ret = null;
        try{
            ret = pjp.proceed(args);
        }catch(Throwable throwable){
            t.printStackTrace();
        }
        return ret;
    }
}

抛出异常后通知获取异常 

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @AfterThrowing(value = "pt()",throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ..."+t);
    }
}

注意:

Spring事务管理 

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

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

commit是用来提交事务,rollback是用来回滚事务。

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

转账案例-需求分析

需求: 实现任意两个账户间转账操作

需求微缩: A账户减钱,B账户加钱

为了实现上述的业务需求,我们可以按照下面步骤来实现下:

①:数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)

②:业务层提供转账操作(transfer),调用减钱与加钱的操作

③:提供2个账号和操作金额执行转账操作

④:基于Spring整合MyBatis环境搭建上述操作

转账案例-环境搭建 

步骤1:准备数据库表

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
);
insert into tbl_account values(1,'Tom',1000);
insert into tbl_account values(2,'Jerry',1000);

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

可见Spring整合MyBatis,另外还需要添加事务管理器和开启事务注解

在JdbcConfig类中配置事务管理器

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;
    }
}

在SpringConfig的配置类中开启事务注解

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

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

public class Account implements Serializable {

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

步骤4:创建Dao接口

public interface AccountDao {

    @Update("update tbl_account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    @Update("update tbl_account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}

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

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;

    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        accountDao.inMoney(in,money);
    }

}

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

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);
        accountDao.inMoney(in,money);
    }
}

注意: 

@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务
  • 写在接口方法上,该接口的所有实现类的该方法都会有事务
  • 写在实现类上,该类中的所有方法都会有事务
  • 写在实现类方法上,该方法上有事务

Spring事务角色

未开启Spring事务之前

开启Spring事务之后

  • transfer上添加了@Transactional注解,在该方法上就会有一个事务T
  • AccountDao的outMoney方法的事务T1加入到transfer的事务T中
  • AccountDao的inMoney方法的事务T2加入到transfer的事务T中
  • 这样就保证他们在同一个事务中,当业务层中出现异常,整个事务就会回滚,保证数据的准确性。

概念:

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

注意:

目前的事务管理是基于DataSourceTransactionManagerSqlSessionFactoryBean使用的是同一个数据源。

Spring事务属性

事务配置

rollbackFor是指定回滚异常。要知道并不是所有的异常都会回滚事务,Spring的事务只会对Error异常RuntimeException异常及其子类进行事务回滚,其他的异常类型是不会回滚的。此时就可以使用rollbackFor属性来设置出现IOException异常回滚

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
	@Transactional(rollbackFor = {IOException.class})
    public void transfer(String out,String in ,Double money) throws IOException{
        accountDao.outMoney(out,money);
        //int i = 1/0; //这个异常事务会回滚
        if(true){
            throw new IOException(); //这个异常事务就不会回滚
        }
        accountDao.inMoney(in,money);
    }

}

转账业务追加日志案例

该环境是基于转账环境来完成的,所以环境的准备可以参考转账案例的环境搭建步骤

步骤1:创建日志表

create table tbl_log(
   id int primary key auto_increment,
   info varchar(255),
   createDate datetime
)

步骤2:添加LogDao接口

public interface LogDao {
    @Insert("insert into tbl_log (info,createDate) values(#{info},now())")
    void log(String info);
}

步骤3:添加LogService接口与实现类

public interface LogService {
    void log(String out, String in, Double money);
}
@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;
	@Transactional
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

步骤4:在转账的业务中添加记录日志

public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    //配置当前接口方法具有事务
    public void transfer(String out,String in ,Double money)throws IOException ;
}
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
    @Autowired
    private LogService logService;
	@Transactional
    public void transfer(String out,String in ,Double money) {
        try{
            accountDao.outMoney(out,money);
            accountDao.inMoney(in,money);
        }finally {
            logService.log(out,in,money);
        }
    }

}

对于上述案例的分析:

  • log方法、inMoney方法和outMoney方法都属于增删改,分别有事务T1,T2,T3
  • transfer因为加了@Transactional注解,也开启了事务T
  • 前面我们讲过Spring事务会把T1,T2,T3都加入到事务T中
  • 所以当转账失败后,所有的事务都回滚,导致日志没有记录下来

步骤5:修改logService改变事务的传播行为

@Service
public class LogServiceImpl implements LogService {

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

SpringMVC 

SpringMVC简介

SpringMVC与Servlet技术功能等同,均属于web层或者说表现层开发技术

SpringMVC与Servlet相比,开发起来更简单快捷,用更少的代码完成表现层代码的开发

例子:

//UserSaveServlet:使用Servlet开发的用户新增模块
@WebServlet("/user/save")
public class UserSaveServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.接收请求参数
        String name = req.getParameter("name");
        System.out.println("servlet save name ==> " + name);
        //2.生产响应
        resp.setContentType("text/json;charset=utf-8");
        PrintWriter pw = resp.getWriter();
        pw.write("{'module':'servlet save'}");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

//UserUpdateServlet:使用Servlet开发的用户修改模块
@WebServlet("/user/update")
public class UserUpdateServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.接收请求参数
        String name = req.getParameter("name");
        System.out.println("servlet update name ==> " + name);
        //2.生产响应
        resp.setContentType("text/json;charset=utf-8");
        PrintWriter pw = resp.getWriter();
        pw.write("{'module':'servlet update'}");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

//UserDeleteServlet:使用Servlet开发的用户删除模块
@WebServlet("/user/delete")
public class UserDeleteServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.接收请求参数
        String name = req.getParameter("name");
        System.out.println("servlet delete name ==> " + name);
        //2.生产响应
        resp.setContentType("text/json;charset=utf-8");
        PrintWriter pw = resp.getWriter();
        pw.write("{'module':'servlet delete'}");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

//UserDeleteServlet:使用Servlet开发的用户查询模块
@WebServlet("/user/select")
public class UserSelectServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.接收请求参数
        String name = req.getParameter("name");
        System.out.println("servlet select name ==> " + name);
        //2.生产响应
        resp.setContentType("text/json;charset=utf-8");
        PrintWriter pw = resp.getWriter();
        pw.write("{'module':'servlet select'}");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

上面就是通过Servlet的方式来实现的用户模块的增删改查功能。下面使用SpringMVC来开发相同的功能:

@Controller
public class UserController {

    @RequestMapping("/save")
    @ResponseBody
    public String save(String name){
        System.out.println("springmvc save name ==> " + name);
        return "{'module':'springmvc save'}";
    }

    @RequestMapping("/delete")
    @ResponseBody
    public String delete(String name){
        System.out.println("springmvc delete name ==> " + name);
        return "{'module':'springmvc delete'}";
    }

    @RequestMapping("/update")
    @ResponseBody
    public String update(String name){
        System.out.println("springmvc update name ==> " + name);
        return "{'module':'springmvc update'}";
    }

    @RequestMapping("/select")
    @ResponseBody
    public String select(String name){
        System.out.println("springmvc select name ==> " + name);
        return "{'module':'springmvc select'}";
    }
}
  • SpringMVC是一种基于Java实现MVC模型的轻量级Web框架

  • 优点

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

SpringMVC入门案例

我们先来回顾下以前Servlet是如何进行开发的:

  1. 浏览器发送请求到Tomcat服务器
  2. Tomcat服务器接收到请求后,会根据请求路径来匹配对应的Servlet,并将请求交给对应的Servlet来处理

所以对于Servlet来说,我们主要完成的是Servlet类的开发以及对应路径的配置。

SpringMVC程序的流程:

  1. 浏览器发送请求到Tomcat服务器
  2. Tomcat服务器接收到请求后,会将请求交给SpringMVC中的==DispatcherServlet[前端控制器]==来处理请求
  3. DispatcherServlet不真正处理请求,只是按照对应的规则将请求分发到对应的Bean对象
  4. Bean对象是有我们自己编写来处理不同的请求,每个Bean中可以处理一个或多个不同的请求url
  5. DispatcherServlet和Bean对象都需要交给Spring容器来进行管理

步骤1:创建Maven项目,导入依赖

<?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>springmvc_01_quickstart</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <port>80</port>
          <path>/</path>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

步骤2:创建控制器类

//2.制作控制器类,等同于Servlet
//2.1必须是一个spring管理的bean
//2.2定义具体处理请求的方法
//2.3设置当前方法的访问路径
//2.4设置响应结果为json数据
//定义表现层控制器bean
@Controller
public class UserController {

    //设置映射路径为/save,即外部访问路径
    @RequestMapping("/save")
    //设置当前操作返回结果为指定json数据(本质上是一个字符串信息)
    @ResponseBody
    public String save(){
        System.out.println("user save ...");
        return "{'info':'springmvc'}";
    }

    //设置映射路径为/delete,即外部访问路径
    @RequestMapping("/delete")
    @ResponseBody
    public String delete(){
        System.out.println("user delete ...");
        return "{'info':'springmvc'}";
    }
}

步骤3:创建配置类

//3.springmvc配置类,本质上还是一个spring配置类
//定义配置类加载Controller对应的bean
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}

步骤4:创建Tomcat的Servlet容器配置类 

//web容器配置类
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    //加载springmvc配置类,产生springmvc容器(本质还是spring容器)
    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;
    }
}

步骤5:Maven Helper插件启动运行项目

步骤6:浏览器访问 

浏览器输入http://localhost/save或者http://localhost/delete进行访问,可以看得如下内容

入门案例工作流程分析 

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

  2. 执行createServletApplicationContext方法,创建了WebApplicationContext对象

  3. 加载SpringMvcConfig配置类

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

  5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法

  6. 执行getServletMappings方法,定义所有的请求都通过SpringMVC

单次请求过程
  1. 发送请求localhost/save

  2. web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理

  3. 解析请求路径/save

  4. 由/save匹配执行对应的方法save()

  5. 执行save()

  6. 检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方

bean加载控制 

在入门案例中我们创建过一个SpringMvcConfig的配置类,前面学习Spring的时候也创建过一个配置类SpringConfig。这两个配置类都需要加载资源

  • config目录存入的是配置类,写过的配置类有:

    • ServletContainersInitConfig
    • SpringConfig
    • SpringMvcConfig
    • JdbcConfig
    • MybatisConfig
  • controller目录存放的是SpringMVC的controller类

  • service目录存放的是service接口和实现类

  • dao目录存放的是dao/Mapper接口

controller、service和dao这些类都需要被容器管理成bean对象

  • SpringMVC加载其相关bean(表现层bean),也就是controller包下的类,只需要将其扫描范围设置到controller
  • Spring控制的bean
    • 业务bean(Service)
    • 功能bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)
  • 在Spring的配置类SpringConfig中使用注解@ComponentScan(com.itheima),当时扫描的范围中其实是已经包含了controller

因为功能不同,要避免Spring错误加载到SpringMVC的bean

  • 方式一:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean
    @Configuration
    @ComponentScan({"com.itheima.service","comitheima.dao"})
    public class SpringConfig {
    }
  • 方式二:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
    @Configuration
    //设置spring配置类加载bean时的过滤规则,当前要求排除掉表现层对应的bean
    //excludeFilters属性:设置扫描加载bean时,排除的过滤规则
    //type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除
    //classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean
    @ComponentScan(value="com.itheima",
        excludeFilters = @ComponentScan.Filter(
            type = FilterType.ANNOTATION,
            classes = Controller.class
        )
    )
    public class SpringConfig {
    }
  • 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]

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

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}

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

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[]{"/"};
    }
}

请求与响应 

设置请求映射路径

团队多人开发,每人设置不同的请求路径,冲突问题该如何解决:为不同模块设置模块名作为请求路径前置

@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'}";
    }
}

优化方案:

@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 delete(){
        System.out.println("user delete ...");
        return "{'module':'user delete'}";
    }

}

请求参数

GET发送参数:

修改pom.xml来解决GET请求中文乱码问题:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.tomcat.maven</groupId>
      <artifactId>tomcat7-maven-plugin</artifactId>
      <version>2.1</version>
      <configuration>
        <port>80</port><!--tomcat端口号-->
        <path>/</path> <!--虚拟目录-->
        <uriEncoding>UTF-8</uriEncoding><!--访问路径编解码字符集-->
      </configuration>
    </plugin>
  </plugins>
</build>

POST发送参数:

配置过滤器来解决POST请求中文乱码问题:

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};
    }
}

 嵌套POJO类型参数的发送

数组和集合类型参数的发送

五种类型参数传递:
  1. 普通参数
  2. POJO类型参数
  3. 嵌套POJO类型参数
  4. 数组类型参数
  5. 集合类型参数
  • 创建一个Web的Maven项目,pom.xml添加依赖

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
  • 创建对应的配置类

    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    }
    
    @Configuration
    @ComponentScan("com.itheima.controller")
    public class SpringMvcConfig {
    }
  • 编写UserController

    @Controller
    public class UserController {
    
        //普通参数:请求参数与形参名称对应即可完成参数传递
        @RequestMapping("/commonParam")
        @ResponseBody
        public String commonParam(String name ,int age){
            System.out.println("普通参数传递 name ==> "+name);
            System.out.println("普通参数传递 age ==> "+age);
            return "{'module':'common param'}";
        }
    
        //普通参数:请求参数名与形参名不同时,使用@RequestParam注解关联请求参数名称与形参名称之间的关系
        @RequestMapping("/commonParamDifferentName")
        @ResponseBody
        public String commonParamDifferentName(@RequestParam("name") String userName , int age){
            System.out.println("普通参数传递 userName ==> "+userName);
            System.out.println("普通参数传递 age ==> "+age);
            return "{'module':'common param different name'}";
        }
    
        //POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
        @RequestMapping("/pojoParam")
        @ResponseBody
        public String pojoParam(User user){
            System.out.println("pojo参数传递 user ==> "+user);
            return "{'module':'pojo param'}";
        }
    
        //嵌套POJO参数:嵌套属性按照层次结构设定名称即可完成参数传递
        @RequestMapping("/pojoContainPojoParam")
        @ResponseBody
        public String pojoContainPojoParam(User user){
            System.out.println("pojo嵌套pojo参数传递 user ==> "+user);
            return "{'module':'pojo contain pojo param'}";
        }
    
        //数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
        @RequestMapping("/arrayParam")
        @ResponseBody
        public String arrayParam(String[] likes){
            System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
            return "{'module':'array param'}";
        }
    
        //集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
        @RequestMapping("/listParam")
        @ResponseBody
        public String listParam(@RequestParam List<String> likes){
            System.out.println("集合参数传递 likes ==> "+ likes);
            return "{'module':'list param'}";
        }
    
    
        //集合参数:json格式
        //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
        //2.使用@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'}";
        }
    
        //POJO参数:json格式
        //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
        //2.使用@RequestBody注解将外部传递的json数据映射到形参的实体类对象中,要求属性名称一一对应
        @RequestMapping("/pojoParamForJson")
        @ResponseBody
        public String pojoParamForJson(@RequestBody User user){
            System.out.println("pojo(json)参数传递 user ==> "+user);
            return "{'module':'pojo for json param'}";
        }
    
        //集合参数:json格式
        //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
        //2.使用@RequestBody注解将外部传递的json数组数据映射到形参的保存实体类对象的集合对象中,要求属性名称一一对应
        @RequestMapping("/listPojoParamForJson")
        @ResponseBody
        public String listPojoParamForJson(@RequestBody List<User> list){
            System.out.println("list pojo(json)参数传递 list ==> "+list);
            return "{'module':'list pojo for json param'}";
        }
    
        //日期参数
        //使用@DateTimeFormat注解设置日期类型数据格式,默认格式yyyy/MM/dd
        @RequestMapping("/dataParam")
        @ResponseBody
        public String dataParam(Date date,
                                @DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
                                @DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2){
            System.out.println("参数传递 date ==> "+date);
            System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
            System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
            return "{'module':'data param'}";
        }
    
    }
JSON数据传输参数:
  • json普通数组(["value1","value2","value3",...])
  • json对象({key1:value1,key2:value2,...})
  • json对象数组([{key1:value1,...},{key2:value2,...}])

1.SpringMVC默认使用的是jackson来处理json的转换,需要在pom.xml添加jackson依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>

2.JSON类型数据的发送

JSON普通数组

JSON对象数据 

 JSON对象数组

3.在SpringMVC的配置类中开启SpringMVC的注解支持,这里面就包含了将JSON转换成对象的功能

@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}

 4.参数前添加@RequestBody(见上面:编写UserController)

@RequestBody与@RequestParam

  • 区别

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

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

参数的发送

SpringMVC默认支持的字符串转日期的格式为yyyy/MM/dd,可以使用@DateTimeFormat进行格式转换

SpringMVC是通过Converter接口实现类型转换的:

  • 请求参数年龄数据(String→Integer)
  • json数据转对象(json → POJO)
  • 日期格式转换(String → Date)

响应

对于响应,主要就包含两部分内容:

  • 响应页面
  • 响应数据
    • 文本数据
    • json数据
@Controller
public class UserController {

    //响应页面/跳转页面
    //返回值为String类型,设置返回值为页面名称,即可实现页面跳转
    @RequestMapping("/toJumpPage")
    public String toJumpPage(){
        System.out.println("跳转页面");
        return "page.jsp";
    }

    //响应文本数据
    //返回值为String类型,设置返回值为任意字符串信息,即可实现返回指定字符串信息,需要依赖@ResponseBody注解
    @RequestMapping("/toText")
    @ResponseBody
    public String toText(){
        System.out.println("返回纯文本数据");
        return "response text";
    }

    //响应POJO对象
    //返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
    @RequestMapping("/toJsonPOJO")
    @ResponseBody
    public User toJsonPOJO(){
        System.out.println("返回json对象数据");
        User user = new User();
        user.setName("itcast");
        user.setAge(15);
        return user;
    }

    //响应POJO集合对象
    //返回值为集合对象,设置返回值为集合类型,即可实现返回对应集合的json数组数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
    @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;
    }
}

通过HTTPMessageConverter接口实现类型转换:

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

Rest风格 

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(删除)

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

RESTful入门案例

  • 编写模型类User和Book

    public class User {
        private String name;
        private int age;
        //getter...setter...toString省略
    }
  • 编写UserController和BookController
    @Controller
    public class UserController {
    
        //设置当前请求方法为POST,表示REST风格中的添加操作
        @RequestMapping(value = "/users",method = RequestMethod.POST)
        @ResponseBody
        public String save(){
            System.out.println("user save...");
            return "{'module':'user save'}";
        }
    
        //设置当前请求方法为DELETE,表示REST风格中的删除操作
        //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
        @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
        @ResponseBody
        public String delete(@PathVariable Integer id){
            System.out.println("user delete..." + id);
            return "{'module':'user delete'}";
        }
    
        //设置当前请求方法为PUT,表示REST风格中的修改操作
        @RequestMapping(value = "/users",method = RequestMethod.PUT)
        @ResponseBody
        public String update(@RequestBody User user){
            System.out.println("user update..."+user);
            return "{'module':'user update'}";
        }
    
        //设置当前请求方法为GET,表示REST风格中的查询操作
        //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
        @RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
        @ResponseBody
        public String getById(@PathVariable Integer id){
            System.out.println("user getById..."+id);
            return "{'module':'user getById'}";
        }
    
        //设置当前请求方法为GET,表示REST风格中的查询操作
        @RequestMapping(value = "/users",method = RequestMethod.GET)
        @ResponseBody
        public String getAll(){
            System.out.println("user getAll...");
            return "{'module':'user getAll'}";
        }
    
    }

如果方法形参的名称和路径{}中的值不一致:

如果有多个参数需要传递:

@Controller
public class UserController {
    //设置当前请求方法为DELETE,表示REST风格中的删除操作
	@RequestMapping(value = "/users/{id}/{name}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id,@PathVariable String name) {
        System.out.println("user delete..." + id+","+name);
        return "{'module':'user delete'}";
    }
}

三个注解@RequestBody@RequestParam@PathVariable:

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

RESTful快速开发

问题1:每个方法的@RequestMapping注解中都定义了访问路径/users,重复性太高。

将@RequestMapping提到类上面,用来定义所有方法共同的访问路径。

问题2:每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。

使用@GetMapping  @PostMapping  @PutMapping  @DeleteMapping代替

问题3:每个方法响应json都需要加上@ResponseBody注解,重复性太高。

1.将ResponseBody提到类上面,让所有的方法都有@ResponseBody的功能
2.使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
//@Controller
//@ResponseBody配置在类上可以简化配置,表示设置当前每个方法的返回值都作为响应体
//@ResponseBody
@RestController     //使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
@RequestMapping("/books")
public class BookController {

//    @RequestMapping( method = RequestMethod.POST)
    @PostMapping        //使用@PostMapping简化Post请求方法对应的映射配置
    public String save(@RequestBody Book book){
        System.out.println("book save..." + book);
        return "{'module':'book save'}";
    }

//    @RequestMapping(value = "/{id}" ,method = RequestMethod.DELETE)
    @DeleteMapping("/{id}")     //使用@DeleteMapping简化DELETE请求方法对应的映射配置
    public String delete(@PathVariable Integer id){
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";
    }

//    @RequestMapping(method = RequestMethod.PUT)
    @PutMapping         //使用@PutMapping简化Put请求方法对应的映射配置
    public String update(@RequestBody Book book){
        System.out.println("book update..."+book);
        return "{'module':'book update'}";
    }

//    @RequestMapping(value = "/{id}" ,method = RequestMethod.GET)
    @GetMapping("/{id}")    //使用@GetMapping简化GET请求方法对应的映射配置
    public String getById(@PathVariable Integer id){
        System.out.println("book getById..."+id);
        return "{'module':'book getById'}";
    }

//    @RequestMapping(method = RequestMethod.GET)
    @GetMapping             //使用@GetMapping简化GET请求方法对应的映射配置
    public String getAll(){
        System.out.println("book getAll...");
        return "{'module':'book getAll'}";
    }
}

RESTful案例

需求一:图片列表查询,从后台返回数据,将数据展示在页面上

需求二:新增图片,将新增图书的数据传递到后台,并在控制台打印

此次案例的重点是在SpringMVC中如何使用RESTful实现前后台交互,所以本案例并没有和数据库进行交互,所有数据使用数据来完成开发。

  • 创建一个Web的Maven项目并添加依赖

<dependencies>
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.10.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.tomcat.maven</groupId>
      <artifactId>tomcat7-maven-plugin</artifactId>
      <version>2.1</version>
      <configuration>
        <port>80</port>
        <path>/</path>
      </configuration>
    </plugin>
  </plugins>
</build>
  •  创建对应的配置类
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};
    }
}

@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig {
}
  • 编写模型类Book
public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
    //setter...getter...toString略
}
  •  编写BookController
@RestController
@RequestMapping("/books")
public class BookController {

    @PostMapping
    public String save(@RequestBody Book book){
        System.out.println("book save ==> "+ book);
        return "{'module':'book save success'}";
    }

    @GetMapping
    public List<Book> getAll(){
        System.out.println("book getAll is running ...");
        List<Book> bookList = new ArrayList<Book>();

        Book book1 = new Book();
        book1.setType("计算机");
        book1.setName("SpringMVC入门教程");
        book1.setDescription("小试牛刀");
        bookList.add(book1);

        Book book2 = new Book();
        book2.setType("计算机");
        book2.setName("SpringMVC实战教程");
        book2.setDescription("一代宗师");
        bookList.add(book2);

        Book book3 = new Book();
        book3.setType("计算机丛书");
        book3.setName("SpringMVC实战教程进阶");
        book3.setDescription("一代宗师呕心创作");
        bookList.add(book3);

        return bookList;
    }

}
  • SpringMVC需要将静态资源进行放行 
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    //设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pages/????时候,从/pages目录下查找内容
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}
  • 修改books.html页面  
<script>
    var vue = new Vue({

        el: '#app',

        data:{
            dataList: [],//当前页要展示的分页列表数据
            formData: {},//表单数据
            dialogFormVisible: false,//增加表单是否可见
            dialogFormVisible4Edit:false,//编辑表单是否可见
            pagination: {},//分页模型数据,暂时弃用
        },

        //钩子函数,VUE对象初始化完成后自动执行
        created() {
            this.getAll();
        },

        methods: {
            // 重置表单
            resetForm() {
                //清空输入框
                this.formData = {};
            },

            // 弹出添加窗口
            openSave() {
                this.dialogFormVisible = true;
                this.resetForm();
            },

            //添加
            saveBook () {
                axios.post("/books",this.formData).then((res)=>{

                });
            },

            //主页列表查询
            getAll() {
                axios.get("/books").then((res)=>{
                    this.dataList = res.data;
                });
            },

        }
    })
</script>

SSM整合

整合配置

创建工程并导入坐标

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

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.tomcat.maven</groupId>
      <artifactId>tomcat7-maven-plugin</artifactId>
      <version>2.1</version>
      <configuration>
        <port>80</port>
        <path>/</path>
      </configuration>
    </plugin>
  </plugins>
</build>
  • 依赖项:使用了 Spring 框架(包括 Web MVC、JDBC、测试模块)、MyBatis(持久层框架)、 MyBatis 和 Spring 框架的集成模块、MySQL 驱动、Druid 连接池、JUnit 测试框架、Servlet API 和 Jackson JSON 库。
  • 构建插件:配置了 Tomcat 7 Maven 插件,用于在开发阶段快速部署和运行项目。

SSM整合

Spring

SpringConfig 

@Configuration
@ComponentScan({"com.itheima.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
MyBatis

MyBatisConfig和JdbcConfig 

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;
    }

}


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;
    }
}
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_db
jdbc.username=root
jdbc.password=root
SpringMVC

ServletConfig和SpringMvcConfig

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

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

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


@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc
public class SpringMvcConfig {
}

功能模块

表与实体类

DROP TABLE IF EXISTS `tbl_book`;
CREATE TABLE `tbl_book`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  --排序规则:utf8_general_ci,不区分大小写的排序规则。
  `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
  --设置 id 字段的自动递增起始值为 13。行存储格式为动态,允许更灵活的存储
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tbl_book
-- ----------------------------
INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Spring实战 第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
INSERT INTO `tbl_book` VALUES (2, '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,手写Spring精华思想');
INSERT INTO `tbl_book` VALUES (3, '计算机理论', 'Spring 5 设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式');
INSERT INTO `tbl_book` VALUES (4, '计算机理论', 'Spring MVC+MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
INSERT INTO `tbl_book` VALUES (5, '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
INSERT INTO `tbl_book` VALUES (6, '计算机理论', 'Java核心技术 卷I 基础知识(原书第11版)', 'Core Java 第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新');
INSERT INTO `tbl_book` VALUES (7, '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,大厂面试知识点全覆盖');
INSERT INTO `tbl_book` VALUES (8, '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉');
INSERT INTO `tbl_book` VALUES (9, '计算机理论', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术');
INSERT INTO `tbl_book` VALUES (10, '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
INSERT INTO `tbl_book` VALUES (11, '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
INSERT INTO `tbl_book` VALUES (12, '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", type='" + type + '\'' +
                ", name='" + name + '\'' +
                ", description='" + description + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

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

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

 dao(接口加自动代理)

public interface BookDao {

//    @Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
    @Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
    public void save(Book book);

    @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
    public void update(Book book);

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

    @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id);

    @Select("select * from tbl_book")
    public List<Book> getAll();
}

service(接口加实现类) 

@Transactional
public interface BookService {

    /**
     * 保存
     * @param book
     * @return
     */
    public boolean save(Book book);

    /**
     * 修改
     * @param book
     * @return
     */
    public boolean update(Book book);

    /**
     * 按id删除
     * @param id
     * @return
     */
    public boolean delete(Integer id);

    /**
     * 按id查询
     * @param id
     * @return
     */
    public Book getById(Integer id);

    /**
     * 查询全部
     * @return
     */
    public List<Book> getAll();
}


@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    public boolean save(Book book) {
        bookDao.save(book);
        return true;
    }

    public boolean update(Book book) {
        bookDao.update(book);
        return true;
    }

    public boolean delete(Integer id) {
        bookDao.delete(id);
        return true;
    }

    public Book getById(Integer id) {
        return bookDao.getById(id);
    }

    public List<Book> getAll() {
        return bookDao.getAll();
    }
}

controller

@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @PostMapping
    public boolean save(@RequestBody Book book) {
        return bookService.save(book);
    }

    @PutMapping
    public boolean update(@RequestBody Book book) {
        return bookService.update(book);
    }

    @DeleteMapping("/{id}")
    public boolean delete(@PathVariable Integer id) {
        return bookService.delete(id);
    }

    @GetMapping("/{id}")
    public Book getById(@PathVariable Integer id) {
        return bookService.getById(id);
    }

    @GetMapping
    public List<Book> getAll() {
        return bookService.getAll();
    }
}

接口测试

业务层接口测试(整合JUnit)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {

    @Autowired
    private BookService bookService;

    @Test
    public void testGetById(){
        Book book = bookService.getById(1);
        System.out.println(book);
    }

    @Test
    public void testGetAll(){
        List<Book> all = bookService.getAll();
        System.out.println(all);
    }

}

表现层接口测试(postman) 

表现层数据封装

前端接收数据格式 

设置统一数据返回结果类

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;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Integer getCode() {
        return code;
    }

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

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

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

设置统一数据返回结果编码

//状态码
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

根据情况设定合理的Result

//统一每一个控制器方法返回值
@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思想

异常处理器:集中的、统一的处理项目中出现的异常

项目异常处理方案

  • 项目异常分类
    • 业务异常(BusinessException)
      • 规范的用户行为产生的异常
      • 不规范的用户行为操作产生的异常
    • 系统异常(SystemException)
      • 项目运行过程中可预计且无法避免的异常
    • 其他异常(Exception)
      • 编程人员未预期到的异常
  • 项目异常处理方案
    • 业务异常(BusinessException)
      • 发送对应消息传递给用户,提醒规范操作
    • 系统异常(SystemException)
      • 发送固定消息传递给用户,安抚用户
      • 发送特定消息给运维人员,提醒维护
      • 记录日志
    • 其他异常(Exception)
      • 发送固定消息传递给用户,安抚用户
      • 发送特定消息给编程人员,提醒维护(纳入预期范围内)
      • 记录日志

1.自定义项目系统级异常

package com.itheima.exception;
//自定义异常处理器,用于封装异常信息,对异常进行分类
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;
    }

}

2.自定义项目业务级异常

package com.itheima.exception;
//自定义异常处理器,用于封装异常信息,对异常进行分类
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;
    }

}

3.自定义异常编码(持续补充)

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;

    public static final Integer SYSTEM_ERR = 50001;
    public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
    public static final Integer SYSTEM_UNKNOW_ERR = 59999;

    public static final Integer BUSINESS_ERR = 60002;



}

4.触发自定义异常

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    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);
    }
}

5.拦截并处理异常

//@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,"系统繁忙,请稍后再试!");
    }
}

前后台协议联调

修改dao和service

public interface BookDao {

//    @Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
    @Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
    public int save(Book book);

    @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
    public int update(Book book);

    @Delete("delete from tbl_book where id = #{id}")
    public int delete(Integer id);

    @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id);

    @Select("select * from tbl_book")
    public List<Book> getAll();
}


@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    public boolean save(Book book) {
        return bookDao.save(book) > 0;
    }

    public boolean update(Book book) {
        return bookDao.update(book) > 0;
    }

    public boolean delete(Integer id) {
        return bookDao.delete(id) > 0;
    }

    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);
    }

    public List<Book> getAll() {
        return bookDao.getAll();
    }
}
<script>
    var vue = new Vue({

        el: '#app',
        data:{
            pagination: {},
            dataList: [],//当前页要展示的列表数据
            formData: {},//表单数据
            dialogFormVisible: false,//控制表单是否可见
            dialogFormVisible4Edit:false,//编辑表单是否可见
            rules: {//校验规则
                type: [{ required: true, message: '图书类别为必填项', trigger: 'blur' }],
                name: [{ required: true, message: '图书名称为必填项', trigger: 'blur' }]
            }
        },

        //钩子函数,VUE对象初始化完成后自动执行
        created() {
            this.getAll();
        },

        methods: {
            //列表
            getAll() {
                //发送ajax请求
                axios.get("/books").then((res)=>{
                    this.dataList = res.data.data;
                });
            },

            //弹出添加窗口
            handleCreate() {
                this.dialogFormVisible = true;
                this.resetForm();
            },

            //重置表单
            resetForm() {
                this.formData = {};
            },

            //添加
            handleAdd () {
                //发送ajax请求
                axios.post("/books",this.formData).then((res)=>{
                    console.log(res.data);
                    //如果操作成功,关闭弹层,显示数据
                    if(res.data.code == 20011){
                        this.dialogFormVisible = false;
                        this.$message.success("添加成功");
                    }else if(res.data.code == 20010){
                        this.$message.error("添加失败");
                    }else{
                        this.$message.error(res.data.msg);
                    }
                }).finally(()=>{
                    this.getAll();
                });
            },

            //弹出编辑窗口
            handleUpdate(row) {
                // console.log(row);   //row.id 查询条件
                //查询数据,根据id查询
                axios.get("/books/"+row.id).then((res)=>{
                    // console.log(res.data.data);
                    if(res.data.code == 20041){
                        //展示弹层,加载数据
                        this.formData = res.data.data;
                        this.dialogFormVisible4Edit = true;
                    }else{
                        this.$message.error(res.data.msg);
                    }
                });
            },

            //编辑
            handleEdit() {
                //发送ajax请求
                axios.put("/books",this.formData).then((res)=>{
                    //如果操作成功,关闭弹层,显示数据
                    if(res.data.code == 20031){
                        this.dialogFormVisible4Edit = false;
                        this.$message.success("修改成功");
                    }else if(res.data.code == 20030){
                        this.$message.error("修改失败");
                    }else{
                        this.$message.error(res.data.msg);
                    }
                }).finally(()=>{
                    this.getAll();
                });
            },

            // 删除
            handleDelete(row) {
                //1.弹出提示框
                this.$confirm("此操作永久删除当前数据,是否继续?","提示",{
                    type:'info'
                }).then(()=>{
                    //2.做删除业务
                    axios.delete("/books/"+row.id).then((res)=>{
                        if(res.data.code == 20021){
                            this.$message.success("删除成功");
                        }else{
                            this.$message.error("删除失败");
                        }
                    }).finally(()=>{
                        this.getAll();
                    });
                }).catch(()=>{
                    //3.取消删除
                    this.$message.info("取消删除操作");
                });
            }
        }
    })

</script>

拦截器

拦截器概念

  • 拦截器(Interceptor)是一种动态拦截方法调用的机制
  • 作用
    • 在指定的方法调用前后执行预先设定后的的代码
    • 阻止原始方法的执行

拦截器与过滤器区别

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

入门案例

  1. 制作拦截器功能类
  2. 配置拦截器的执行位置 

声明拦截器的bean,并实现HanderInterceptor接口(注意:扫描加载bean)

@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    //原始方法调用前执行的内容
    //返回值类型可以拦截控制的执行,true放行,false终止
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle..."+contentType);
        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...");
    }
}

定义一个配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法(注意:扫描加载配置)。添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个

@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/*");
    }
}

使用标准接口WebMvcConfigurer简化开发(注意:侵入式较强)

@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/*");
    }
}

执行流程

拦截器参数 

前置处理

  • 参数
    • request:请求对象
    • response:响应对象
    • handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装
  • 返回值
    • 返回值为false,被拦截的处理器将不执行

后置处理

  • 参数
    • modelAndview:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整

完成后处理

  • 参数
    • ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理

多拦截器执行顺序

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

 

SpringBoot

SpringBoot简介

入门案例

工程创建

创建新模块,选择springboot

选中 Web,然后勾选 Spring Web由于我们需要开发一个 web 程序,使用到了 SpringMVC 技术,所以按照下图进行勾选

在 com.itheima.controller 包下创建 BookController ,代码如下:

@RestController
@RequestMapping("/books")
public class BookController {

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

启动服务器(在创建好的工程中不需要创建配置类,也不需要使用本地的tomcat和插件,它会帮我们自动生成一个 Application 类,运行即可在控制台看出如下信息)

测试

程序所包含的基础文件
<?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>
对比

Spring 程序和 SpringBoot 程序

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

快速启动 
  • 后端可以将 SpringBoot 工程打成 jar 包,该 jar 包运行不依赖于 Tomcat 和 Idea 这些工具也可以正常运行,只是这个 jar 包在运行过程中连接和我们自己程序相同的 Mysql 数据库即可。 
  • 使用 Maven 的 package 指令打包就会在 target 目录下生成对应的 Jar 包。
  • 进入 jar 包所在位置,在 命令提示符 中输入如下命令

    jar -jar springboot_01_quickstart-0.0.1-SNAPSHOT.jar

 

注意:打jar包时需要配置如下插件,该插件必须配置,不然打好的 jar 包也是有问题的。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

SpringBoot概述

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

原始 Spring 环境搭建和开发存在以下问题:

  • 配置繁琐
  • 依赖设置繁琐

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

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

starter

  • SpringBoot 中常见项目名称,定义了当前项目使用的所有项目坐标,以达到减少依赖配置的目的

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辅助依赖(切换web服务器) 

切换 web 服务器就需要将默认的 tomcat 服务器给排除掉:使用 exclusion 标签

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

引入jetty 服务器的起步依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
程序启动

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

@SpringBootApplication
public class Springboot01QuickstartApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(Springboot01QuickstartApplication.class, args);
    }
}
  • SpringBoot 在创建项目时,采用jar的打包方式

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

配置文件

配置文件格式

启动服务器默认的端口号是 8080,访问路径可以书写为

http://localhost:8080/books/1

在线上环境我们还是希望将端口号改为 80,这样在访问的时候就可以不写端口号了,如下

http://localhost/books/1

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

  • application.properties

    server.port=80
    
  • application.yml

    server:
    	port: 81
    
  • application.yaml

    server:
    	port: 82
    

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

三种配置文件的优先级是:application.properties > application.yml > application.yaml

yaml格式

YAML(YAML Ain't Markup Language),一种数据序列化格式。这种格式的配置文件在近些年已经占有主导地位

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

<enterprise>
    <name>itcast</name>
    <age>16</age>
    <tel>4006184000</tel>
</enterprise>

而 properties 类型的配置文件如下

enterprise.name=itcast
enterprise.age=16
enterprise.tel=4006184000

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

enterprise:
	name: itcast
	age: 16
	tel: 4006184000

优点:

  • 容易阅读

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

  • 容易与脚本语言交互

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

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

YAML 文件扩展名:

  • .yml (主流)
  • .yaml

语法规则

  • 大小写敏感

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

  • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)

    空格的个数并不重要,只要保证同层级的左侧对齐即可。

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

  • # 表示注释

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

    likes:
      - music
      - game
      - PE

yaml配置文件数据读取

配置文件如下:

lesson: SpringBoot

server:
  port: 80

enterprise:
  name: itcast
  age: 16
  tel: 4006184000
  subject:
    - Java
    - 前端
    - 大数据

使用 @Value("表达式") 注解可以从配合文件中读取数据,注解中用于读取属性名引用方式是:${一级属性名.二级属性名……}

在 BookController 中使用 @Value 注解读取配合文件数据

@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!";
    }
}

上面方式读取到的数据特别零散,SpringBoot 还可以使用 @Autowired 注解注入 Environment 对象的方式读取数据。这种方式 SpringBoot 会将配置文件中所有的数据封装到 Environment 对象中,如果需要使用哪个数据只需要通过调用 Environment 对象的 getProperty(String name) 方法获取。

@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 中进行注入

Enterprise 实体类内容如下:

@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 内容如下:

@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.yaml文件,在 application.yml 中使用 --- 来分割不同的配置,内容如下:

#设置启用的环境
spring:
  profiles:
    active: dev

---
#开发
spring:
  profiles: dev
server:
  port: 80
---
#生产
spring:
  profiles: pro
server:
  port: 81
---
#测试
spring:
  profiles: test
server:
  port: 82
---

在上面配置中给不同配置起名字的 spring.profiles 配置项已经过时。最新用来起名字的配置项是

#开发
spring:
  config:
    activate:
      on-profile: dev

2.properties 类型的配置文件配置多环境需要定义不同的配置文件

  • application-dev.properties 是开发环境的配置文件。我们在该文件中配置端口号为 80

    server.port=80
    
  • application-test.properties 是测试环境的配置文件。我们在该文件中配置端口号为 81

    server.port=81
    
  • application-pro.properties 是生产环境的配置文件。我们在该文件中配置端口号为 82

    server.port=82
    

SpringBoot 只会默认加载名为 application.properties 的配置文件,所以需要在 application.properties 配置文件中设置启用哪个配置文件,配置如下:

spring.profiles.active=pro

3.命令行启动参数设置 

使用 SpringBoot 开发的程序以后都是打成 jar 包,通过 java -jar xxx.jar 的方式启动服务的。而 SpringBoot 提供了在运行 jar 时设置开启指定的环境的方式,如下

java –jar xxx.jar –-spring.profiles.active=test

临时修改端口号也是可以的,可以通过如下方式

java –jar xxx.jar –-server.port=88

当然也可以同时设置多个配置,比如即指定启用哪个环境配置,又临时指定端口,如下

java –jar springboot.jar –-server.port=88 –-spring.profiles.active=test

4.Maven与SpringBoot多环境兼容 

maven中设置多环境属性并对资源文件开启对默认占位符的解析

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <useDefaultDelimiters>true</useDefaultDelimiters>
                </configuration>
            </plugin>
        </plugins>
</build>
<profiles>
        <!--开发环境-->
        <profile>
            <id>dev</id>
            <properties>
                <profile.active>dev</profile.active>
            </properties>
        </profile>
        <!--生产环境-->
        <profile>
            <id>pro</id>
            <properties>
                <profile.active>pro</profile.active>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <!--测试环境-->
        <profile>
            <id>test</id>
            <properties>
                <profile.active>test</profile.active>
            </properties>
        </profile>
</profiles>

springboot中引用属性

#设置启用的环境
spring:
  profiles:
    active: ${profile.active}

---
#开发
spring:
  profiles: dev
server:
  port: 80
---
#生产
spring:
  profiles: pro
server:
  port: 81
---
#测试
spring:
  profiles: test
server:
  port: 82
---

配置文件分类

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

SpringBoot 中4级配置文件放置位置:(级别越高优先级越高)

  • 1级:classpath:application.yml
  • 2级:classpath:config/application.yml
  • 3级:file :application.yml
  • 4级:file :config/application.yml

SpringBoot整合junit

在 com.itheima.service 下创建 BookService 接口,内容如下

public interface BookService {
    public void save();
}

在 com.itheima.service.impl 包写创建一个 BookServiceImpl 类,使其实现 BookService 接口,内容如下

@Service
public class BookServiceImpl implements BookService {
    @Override
    public void save() {
        System.out.println("book service is running ...");
    }
}

编写测试类

在 test/java 下创建 com.itheima 包,在该包下创建测试类,将 BookService 注入到该测试类中

@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

  • 创建新模块,选择 SpringBoot,并配置模块相关基础信息
  • 选择当前模块需要使用的技术集(MyBatis、MySQL)

  • 在 com.itheima.domain 包下定义实体类 Book,内容如下
public class Book {
    private Integer id;
    private String name;
    private String type;
    private String description;
    
    //setter and  getter
    
    //toString
}
  • 在 com.itheima.dao 包下定义 BookDao 接口,内容如下

//Mybatis会扫描接口并创建接口的代码对象交给Spring管理,@Mapper告诉Mybatis哪个是dao接口。
@Mapper
public interface BookDao {
    @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id);
}

在 test/java 下定义包 com.itheima ,在该包下测试类,内容如下

@SpringBootTest
class Springboot08MybatisApplicationTests {

	@Autowired
	private BookDao bookDao;

	@Test
	void testGetById() {
		Book book = bookDao.getById(1);
		System.out.println(book);
	}
}
  • 编写配置。我们代码中并没有指定连接哪儿个数据库,用户名是什么,密码是什么。所以这部分需要在 SpringBoot 的配置文件中进行配合。

在 application.yml 配置文件中配置如下内容

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db
    username: root
    password: root

注意:SpringBoot 版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区 jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC,或在MySQL数据库端配置时区解决此问题

  • 使用Druid数据源

现在我们并没有指定数据源,SpringBoot 有默认的数据源,我们也可以指定使用 Druid 数据源,按照以下步骤实现

1.导入 Druid 依赖

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

2.在 application.yml 配置文件配置,通过 spring.datasource.type 来配置使用什么数据源。

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小帅乱撞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值