Spring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.2.RELEASE</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</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>8.0.27</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- AOP必须 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
1
2
3
4
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/DataBaseName?characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=csh20011103

bean

bean基础配置

bean基础配置

  1. **名称:**bean

  2. **类型:**标签

  3. **所属:**beans标签

  4. **功能:**定义

  5. 格式:

    1
    2
    3
    4
    <beans>
    <bean/>
    <bean></bean>
    </beans>
  6. 属性列表:

    • **id:**bean的id,使用容易可以通过id值获取对应的bean,在一个容器中id值唯一
    • **class:**bean的类型,即配置的bean的全部路径类名
  7. 范例:

    1
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
  8. 配置两者关系:

    1
    2
    3
    4
    5
    6
    7
    8
    public BookDaoImpl{
    ...
    }
    public BookServiceImpl{
    BookDao book_dao;
    ...
    }
    此时BookServiceImpl包含BookDao
    1
    2
    3
    4
    5
    <bean id="bookDao_id" class="com.itheima.dao.impl.BookDaoImpl"/>

    <bean id="bookServie" class="com.itheima.service.impl.BookServiceImpl">
    <property name="book_Dao" ref="bookDao_id">
    </bean>

bean别名配置

1
<bean id="bookDao_id" name="other_name other_name2..." class="com.itheima.dao.impl.BookDaoImpl"/>

name后面起别名,如需多个可以用空格,逗号,分号分割

bean作用范围配置

1
<bean id="bookServie" class="com.itheima.service.impl.BookServiceImpl" scope=""/>

scope可选范围:

  • singleton:单例(默认)
  • prototype:非单例
bean作用范围说明

适合交给容器进行管理的bean

  • 表现层对象
  • 业务层对象
  • 数据层对象
  • 工具对象

不适合交给容器进行管理的bean

  • 封装实体的域对象

bean实例化

构造方法

  • bean本质是就是对象,创建bean使用构造方法完成

如果无参构造方法不存在则抛出异常

静态工厂

1
2
3
4
5
6
public class OrderDaoFactory{
public static OrderDao getOrderDao(){
return new OrderDaoImpl;
}
}
这里的static不能去掉!
1
2
<bean id="orderDao" class="路径" factory-method="所需要调用的该工厂中的方法名"/>
该方法名必须要有static!

实例工厂与FactoryBean

方法一(了解)
1
2
3
4
5
public class UserDaoFactory{
public UserDao getUrderDao(){
return new UserDaoImpl;
}
}
1
2
3
<bean id="userFactory" class="路径"/>

<bean id="uerDao" factory-method="所需要调用的该工厂中的方法名" factory-bean="userFactory"/>
方法二

先创建一个类去接入FactoryBean

1
2
3
4
5
6
7
8
9
public class UserDaoFactoryBean implements FactoryBean<对象类型>{
public UserDao getObject throws Exception{
return new UserDaoImpl();
}

public Class<?> getObjectType(){
return UserDao.class;
}
}
1
<bean id="userDao" class="路径"/>

bean的生命周期

生命周期:从从创建到销毁的整体过程

  • 初始化容器

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

    2. 执行构造方法

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

    4. 执行bean初始化方法

      1
      <bean id="userFactory" class="路径" init-method="初始化执行方法名称" destroy-method="销毁方法名称"/>
  • 使用bean

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

    1. 执行bean销毁方法:设置”钩子”或者关闭容器

bean销毁时机

  • 容器关闭前触发bean的销毁

  • 容器关闭方式:

    • 手工关闭容器

      1
      ConfigurableApplicationContext接口close()操作
    • 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机

      1
      2
      ConfigurableApplicationContext接口registerShutDownHook()操作
      该操作可以写在任何地方

依赖注入

setter注入(推荐)

1
2
3
4
5
<bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>
<bean id="userService" class="org.example.service.impl.UserServiceImpl">
<property name="简单类型" value="初始化数值"/>
<property name="userDao(非简单类型)" ref="userDao"/>
</bean>

构造器注入(耦合度较高)

1
2
3
4
5
<bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>
<bean id="userService" class="org.example.service.impl.UserServiceImpl">
<constructor-arg name="形参" value="初始化数值"/>
<constructor-arg name="userDao(目标所包含非简单类型)" ref="userDao"/>
</bean>

解决参数类型重复问题,使用位置解决参数匹配

1
2
3
4
5
<bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>
<bean id="userService" class="org.example.service.impl.UserServiceImpl">
<constructor-arg index="0" name="形参" value="初始化数值"/>
<constructor-arg index="1" name="userDao(目标所包含非简单类型)" ref="userDao"/>
</bean>

依赖注入方式选择

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
  2. 可选依赖使用setter注入进行,灵活性强
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化(严谨)
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
  5. 自己开发的模块推荐使用setter注入

自动装配

1
2
<bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>
<bean id="userService" class="org.example.service.impl.UserServiceImpl" autowire="">

autowire可选:byType或者byName

不推荐使用byname,耦合度高

集合注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<bean id="userDao" class="org.example.dao.impl.UserDaoImpl">
<property name="array(变量名)">
<array>
<value>value</value>
<value>value</value>
</array>
</property>
<property name="list">
<list>
<value>value</value>
<value>value</value>
</list>
</property>
<property name="set">
<set>
<value>value</value>
<value>value</value>
</set>
</property>
<property name="map">
<map>
<entry key="t_key" value="t_value"/>
<entry key="t_key" value="t_value"/>
</map>
</property>
<property name="propertier">
<props>
<prop key="t_key">value</prop>
<prop key="t_key">value</prop>
</props>
</property>
</bean>

加载properties文件

  • 开启context命名空间
1
2
3
4
5
6
7
8
9
<?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">
只用复制原来的再讲bean改成context即可
</beans>
  • 使用context命名空间,加载指定properties文件
1
2
3
4
5
6
7
8
9
10
11
<context:property-placeholder location="名字.properties"/>

多个配置文件:
<context:property-placeholder location="classpath:*.properties"/>

框架中properties文件:
<context:property-placeholder location="classpath*:*.properties"/>
多了个*号

禁止加载系统:
<context:property-placeholder location="classpath:*.properties" system-properties-mod="NEVER"/>
  • 使用${}读取加载的属性值
1
jdbc.name=value
1
<property name="username" value="${jdbc.name}"/>

注解开发

注解开发定义bean

扫描组件

方案一

1
2
写在xml里
<context:component-scan base-package="org.example.dao.impl"/>

方案二

配置结构转换为**@Configuration**

配置文件中的扫描换成**@ComponentScan(“路径”)**

如果要添加多个路径需要数组形式,即:

@ComponentScan(“{路径1, 路径1…}”)

1
2
3
4
5
写配置类代替配置文件
@Configuration
@ComponentScan("路径")
public class SpringConfig{
}

此时加载配置文件(换接口),其他都一样

1
2
3
4
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.calss);

换之前的名字叫:
ClassPathXmlApplicationContext

定义bean

  • spring提供@Component注解的三个衍生注解(用法一样,只是为了便于区分)
    • @Controller:用于表现层bean定义
    • @Service:用于业务层bean定义
    • @Respository:用于数据层定义
1
2
3
4
5
6
7
8
9
10
11
@Respository("bookDao")
public class BookDaoImpl implements BookDao{
...
}

@Respository
public class BookDaoImpl implements BookDao{
...
}

有名字可以根据名字获得,无名字根据类型获得

bean的作用范围

@Scope用来说明是否为单例

1
2
3
4
5
6
7
8
9
10
11
@Respository
@Scope("prototype")非单例
public class BookDaoImpl implements BookDao{
...
}

@Scope("singleton")单例
@Respository
public class BookDaoImpl implements BookDao{
...
}

生命周期

@PostConstruct构造方法后

@Destroy销毁前

1
2
3
4
5
6
7
8
9
10
11
12
@Respository
public class BookDaoImpl implements BookDao{
@PostConstruct
public void init(){
...
}

@Destroy
public void destroy{
...
}
}

自动装配

非简单类型

@Autowired使用反射中的暴力反射,setter可以不用写,可以写在任意位置

1
2
3
4
5
6
7
8
@Respository
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
public void save(){
...
}
}

如果有相同bean,支持按名称注入,但是不推荐一般使用:

**@Qualifier(名称)**指定加载

1
2
3
4
5
6
7
8
9
@Respository
public class BookServiceImpl implements BookService{
@Autowired
@Qualifier("bookDao")
private BookDao bookDao;
public void save(){
...
}
}

简单类型

@Value可以进行简单类型的注入

1
2
3
4
5
6
7
8
@Respository
public class BookServiceImpl implements BookService{
@Value("value")
private String name;
public void save(){
...
}
}

**@PropertySource(“文件名”)**写入配置类可以实现加载文件

1
jdbc.name=value
1
2
3
4
5
@Configuration
@ComponentScan("路径")
@PropertySource("文件名")
public class SpringConfig{
}
1
2
3
4
5
6
7
8
@Respository
public class BookServiceImpl implements BookService{
@Value("${jdbc.name}")
private String name;
public void save(){
...
}
}

如果要传入多个文件用数组形式:

@PropertySource(“{文件1, 文件2, …}”)

此处不允许使用*****号

第三方bean管理

没有配置只能编程写

方法一

@Bean把方法返回值作为bean

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class SpringConfig{

//定义一个方法获得要管理的对象,方法返回值作为bean
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
...
return dataSource;
}
}

方法二

**@Import(名称)**导入配置

如果有多个需要用数组来进行导入

@Import(“{名称1, 名称2, …}”)

1
2
3
4
5
6
7
8
9
10
11
12
不需要@Configuration
public class JdbcConfig{

//定义一个方法获得要管理的对象,方法返回值作为bean
@Bean
必须有bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
...
return dataSource;
}
}
1
2
3
4
@Configuration
@Import("JdbcConfig")
public class SpringConfig{
}

或者可以使用扫描配置**@ComponentScan进行,但是不推荐**

第三方bean依赖注入

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

AOP

简介

  • AOP面向切面编程,一种编程范式,知道开发者如何组织程序结构
    • OOP面向对象编程
  • 作用:在不惊动原始设计基础上为其进行功能增强
  • spring理念:无侵入式

核心理念

  • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法,抛出异常,设置变量等
    • 在SpringAOP中,理解为方法的执行
  • 切入点(PointCut):匹配连接点的式子
    • 在Spring中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
      • 一个具体方法:com.test.dao包下的BookDao接口中的无形参无返回值的save方法
      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
  • 通知(Advice):在切入点处执行的操作,也就是共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类
  • 切面(Aspect):描述通知与切入点的对应关系

示例

dao

1
2
3
4
5
6
7
8
9
10
11
12
package org.example.dao.impl;

@Repository
public class TestDaoImpl {
public void save1(){
System.out.println("running save1 ...");
}

public void save2(){
System.out.println("running save2 ...");
}
}

通知类

@Pointcut(“execution(返回值类型 目标方法路径”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example.app;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

//设置为bean
@Component
//设置为AOP
@Aspect
public class MyAdvice {

//随便起个名字,设置切点Pointcut
@Pointcut("execution(void org.example.dao.impl.TestDaoImpl.save2())")
private void test(){};

//需要添加的方法,名字随便起,这里起名为method
@Before("test")
public void method(){
System.out.println("running method ...");
}
}

配置类

1
2
3
4
5
6
7
8
9
10
11
12
package org.example.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("org.example")
//告知里面含有AOP
@EnableAspectJAutoProxy
public class SpringConfig {
}

AOP工作流程

工作流程

  1. Spring容器启动
  2. 读取所有切面配置中的切入点(配置的切入点(配置并使用))
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
    • 匹配失败,创建对象
    • 匹配成功,创建原始对象(目标对象)的代理对象
      jdk的代理:增强
      Spring内部AOP通过代理实现
  4. 获取bean执行方法
    • 获取bean,调用方法并执行,完成操作
    • 获取的bean是代理对象时,根据代理对象的运行模式运行源石方法与增强的内容,完成操作

核心概念

  • 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成工作的
  • 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

SpringAOP本质

SpringAOP本质是代理模式

AOP切入点表达式

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

语法格式

  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 报名.类/接口名.方法名(参数) 异常名)

    1
    execution(public User org.example.dao.service.UserService.findById(int))
    • 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
    • 访问修饰符:public,private等,可以省略
    • 返回值
    • 包名
    • 类/接口名
    • 方法名
    • 参数
    • 异常名:方法定义中抛出指定异常,可以省略

通配符

  • 可以使用通配符描述切入点,快速描述(可以参考正则表达式)

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

      1
      2
      *出现在参数代表必有一个参数
      execution(public * org.example.*.service.UserService.find*(*))

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

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

      1
      2
      ..出现在参数代表参数可有可无,可以有多个参数
      execution(public User com..UserService.findById(..))

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

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

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

书写技巧

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

AOP通知类型

  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
  • AOP通知共分为了五种类型
    • 前置通知
    • 后置通知
    • 环绕通知(重点)
    • 返回后通知(了解)
    • 抛出异常后通知(了解)

前置通知

@Before

后置通知

@After

环绕通知(重要)

@Around

需要在方法中加入ProceedingJoinPoint 变量名,用这个变量使用**.proceed()方法进行指定位置,同时需要抛出Throwable异常**,因为原始操作不确定有无异常

1
2
3
4
5
6
@Around("test()")
public void method(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("running method Before ...");
pjp.proceed();
System.out.println("running method After ...");
}

原始方法有返回值

1
2
3
4
5
6
7
8
返回值类型必须要改成Object,用临时变量存储原始方法返回值结果并返回,如有需要可以强转
@Around("test()")
public Object method(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("running method Before ...");
Object temp = pjp.proceed();
System.out.println("running method After ...");
return temp;
}

注意事项

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

ProceedingJoinPoint

ProceedingJoinPoint.getSignature();获取签名信息

1
2
3
4
5
Signature signature = ProceedingJoinPoint.getSignature();

signature.getDeclaringType();//对应类型
signature.getName();//对应方法名
signature.getDeclaringTypeName();//对应类型名

返回后通知

@AfterReturning

只有原始方法不抛出异常的时候才运行该通知

抛出异常后通知

@AfterThrowing

只有原始方法抛出异常的时候才运行该通知

AOP获取通知数据

数据有三种:

  • 原始操作的参数
    获取切入点方法的参数:
    • JoinPoint:适用于前置,后置,返回后,抛出异常后通知
    • ProceedingJoinPoint:适用于环绕通知
  • 原始操作的返回值
    获取切入点方法返回值
    • 返回后通知
    • 环绕通知
  • 原始操作的异常
    获取切入点方法运行异常信息
    • 抛出异常后通知
    • 环绕通知

获取参数

使用JoinPoint里面的**.getArgs()方法,返回值类型为Object类型数组**

可以用Arrays.toString(args)来把里面的类型都转换为字符串

1
2
3
4
@Before("test()")
public void method(JoinPoint jp){
Object args = jp.getArgs();
}

ProceedingJoinPoint也有一样的方法

ProceedingJoinPoint里面的**.proceed()方法里面可以传一个Object数组**,即getArgs()方法获得的Object数组,如果将同类型数组放入.proceed()方法,即.proceed(args),那么就会更改传入原始方法中的参数

获取返回值

正常情况下接收参数直接返回

特殊:@AfterReturning

1
2
3
4
5
6
需要先定义一个Object放入参数中,表示要用这个Object接收返回值
在注解中加上如下,returning = "用来接收返回值的参数名"
@AfterReturning(value = "test()",returning = "ret")
public void method(Object ret) {
...
}

如果参数中同时包含JoinPoint,那么JoinPoint必须在前

1
2
3
4
@AfterReturning(value = "test()",returning = "ret")
public void method(JoinPoint jp, Object ret) {
...
}

获取异常

正常情况下把抛出异常改为内部的try..catch…环绕即可

特殊:@AfterThrowing

1
2
3
4
@AfterThrowing(value = "test()", throwing = "t")
public void method(Throwable t) {
...
}

Spring事务

Spring事务简介

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

Spring事务角色

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

在方法上加上**@Transactional**即可保证方法中所有事务同成功同失败,如果其中一步出现了错误则全部会回滚

@Transactional被称为事务管理员,内部事务被称为事务协调员

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao account;

这里也可以加在他的接口上
@Transactional -> 事务管理员
public void transfer(String username1, String username2, int money){
account.outMoney(username1, money); -> 事务协调员
account.inMoney(username2, money); -> 事务协调员
}
}

事务相关配置

配置属性

在**@Transactional**里面写属性,例如@Transactional(XXX)

属性 作用 示例
readOnly 设置是否为只读事务 readOnly=true
timeout 设置事务超时时间 timeout=-1(永不超时)
rollbackFor 设置事务回滚异常(class) rollbackFor={NullPointException.class}
rollbackForClassName 设置事务回滚异常(String) 同上格式为字符串
noRollbackFor 设置事务不回滚异常(class) noRollbackFor={NullPointException.class}
noRollbackForClassName 设置事务不回滚异常(String) 同上格式为字符串
propagation 设置事务传播行为

事务传播行为

事务传播行为:事务协调员对事务管理员所携带事务的处理态度

1
2
3
4
5
6
7
8
9
10
11
@Transactional
public void transfer(String username1, String username2, int money){
try {
// 以下两个属于一个事务
account.outMoney(username1, money);
account.inMoney(username2, money);
}finally {
// 单独一个事务
logService.logIn(username1, username2, money);
}
}
1
2
3
4
5
6
public interface LogService {

// 括号里的就是事务的传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
void logIn(String username1, String username2, int money);
}
  • REQUIREO:
    • 开始T:加入T
    • 无:新建T2
  • REQUIRES_NEW:
    • 开启T:新建T2
    • 无:新建T2
  • SUPPORTS:
    • 开启T:加入T
    • 无:无
  • NOT_SUPPORTS:
    • 开启T:无
    • 无:无
  • MANDATORY:
    • 开启T:加入T
    • 无:ERROR
  • NEVER:
    • 开启T:ERROR
    • 无:无
  • NESTED:
    设置savePoint,一旦事务回滚,事务将回滚到savePoint处,交由客户响应提交/回滚

SpringMVC

SpringMVC概述

SpringMVC技术与Servlet技术功能等同,均属于web层开发技术,开发比Servlet更简单

  1. SpringMVC是一种表现层框架技术
  2. SpringMVC用于进行表现层功能开发
  • SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
  • 优点
    • 使用简单,开发便捷(相比于Servlet)
    • 灵活性强

导包

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

入门案例

  1. 创建SpringMVC控制器类(等同于Servlet)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 使用Controller定义bean
    @Controller
    public class UserController {
    // 设置当前操作的访问路径
    @RequestMapping("/save")
    // 设置当前操作的返回值类型
    @ResponseBody
    public String save(){
    System.out.println("user save ...");
    return "{'key': 'value'}";
    }
    }
  2. 初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应的bean

    1
    2
    3
    4
    5
    6
    // 这里千万不能@Import(Servlet容器)!!!

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 注意这里继承了AbstractDispatcherServletInitializer

    public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    @Override
    protected WebApplicationContext createServletApplicationContext() {

    // 注意这里因为是Web环境,所以是AnnotationConfigWebApplicationContext
    AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
    // 注册
    ctx.register(SpringMvcConfig.class);
    return ctx;
    }

    @Override
    protected String[] getServletMappings() {
    // 所有请求都交由SpringMVC处理
    return new String[]{"/"};
    }

    @Override
    protected WebApplicationContext createRootApplicationContext() {
    return null;
    }
    }

总结

  • SpringMVC入门程序开发总结(1+N)

    • 一次性工作
      • 创建工程,设置服务器,加载工程
      • 导入坐标
      • 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
      • SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
    • 多次工作
      • 定义处理请求的控制类
      • 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)
  • AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类

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

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

      1
      2
      3
      4
      5
      protected WebApplicationContext createServletApplicationContext() {
      AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
      ctx.register(SpringMvcConfig.class);
      return ctx;
      }
    • getServletMappings()方法,设定SpringMVC对应的请求映射路径,设置为/表示拦截所有请求,任意请求都将转入到SpringMVC进行处理

      1
      2
      3
      protected String[] getServletMappings() {
      return new String[]{"/"};
      }
    • createRootApplicationContext()方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方法同createRootApplicationContext()

      1
      2
      3
      protected WebApplicationContext createRootApplicationContext() {
      return null;
      }

@Controller

  • 名称:@Controller

  • 类型:类注解

  • 位置:SpringMVC控制器类定义上方

  • 作用:设定SpringMVC的核心控制器bean

  • 范例:

    1
    2
    3
    @Controller
    public class UserController {
    }

@RequestMapping

  • 名称:@RequestMapping

  • 类型:方法注解

  • 位置:SpringMVC控制器方法定义上方

  • 作用:设置当前控制器方法请求访问路径

  • 范例:

    1
    2
    3
    4
    @RequestMapping("/save")
    public void save(){
    System.out.println("user save ...");
    }
  • 相关属性

    • value(默认):请求访问路径

@ResponseBody

  • 名称:@ResponseBody

  • 类型:方法注解

  • 位置:SpringMVC控制器类定义上方

  • 作用:设置当前控制器方法相应内容为当前返回值,无需解析

  • 范例:

    1
    2
    3
    4
    5
    6
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
    System.out.println("user save ...");
    return "{'key': 'value'}";
    }

bean加载控制

不能让Spring控制的bean加载到SpringMVC的bean

解决方法:

  1. Spring加载的bean设定扫描范围为com.mvc,排除掉controller内的bean
  2. Spring加载的bean设定扫描范围为精准范围,例如service包,dao包等
  3. 不区分Spring与SpringMVC的环境,加载到同一个环境中

方法二@ComponentScan

includeFilters设定包含的过滤器

excludeFilters设定不包含的过滤器

1
2
3
4
5
6
@ComponentScan(value = "com.mvc",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION, // 设置需要排除的类型(此处为排除注解)
classes = Controller.class // 排除@Controller
)
)

方法三

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

// SpringMVC加载配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}

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

// Spring加载配置
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}

简化

更改原来Web加载容器

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

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

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

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

请求与响应

请求映射路径

不能放相同路径

分块解决,增加请求模块前缀

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/user")
public class UserController {

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

请求方式

后台不区分GET请求和POST请求

后台直接传入参数即可

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/user")
public class UserController {

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

乱码处理

设定过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

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

// 过滤器设置针对post
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}

请求参数

简单类型

如果发送名与接收名不匹配则接收不到,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
localhost/user/save?otherName=test

@Controller
@RequestMapping("/user")
public class UserController {

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

使用**@RequestParam(发送名)**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
User类
package com.mvc.pojo;

public class User {
private String name;
private int id;
private Address address;

getter()方法省略
setter()方法省略

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

接收,只要User里面跟传入的key匹配即可自动装配

1
localhost/user/save?name=haog&id=123&address.son=**&...
1
2
3
4
5
6
@RequestMapping("/save")
@ResponseBody
public String save(User user){
System.out.println(user);
return "{'key': 'value'}";
}

如果要接收数组,只要传入的时候名字一样即可

1
localhost/user/save?likes=**&likes=***&***
1
2
3
4
5
6
@RequestMapping("/save")
@ResponseBody
public String save(String[] likes){
System.out.println(likes);
return "{'key': 'value'}";
}

如果传入集合则需要加上**@RequestParam**,url传入和数组一样

1
2
3
4
5
6
@RequestMapping("/save")
@ResponseBody
public String save(@RequestParam List<String> likes){
System.out.println(likes);
return "{'key': 'value'}";
}

Json数据参数传递

导包
1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
json传递

SpringMvcConfig中添加**@EnableWebMvc**开启转为json为对象的功能

1
2
3
4
5
@Configuration
@ComponentScan("com.mvc")
@EnableWebMvc
public class SpringMvcConfig {
}

controller的请求体里加入**@RequestBody**

1
2
3
4
5
6
@RequestMapping("/save")
@ResponseBody
public String save(@RequestBody List<String> likes){
System.out.println(likes);
return "{'key': 'value'}";
}

传入pojo,方法一样

1
2
3
4
5
6
@RequestMapping("/save")
@ResponseBody
public String save(@RequestBody User user){
System.out.println(user);
return "{'key': 'value'}";
}

日期类型

默认标准格式为yyyy/MM/dd

如果不是标准格式需要用**@DateTimeFormat(pattern = “yyyy-MM-dd”)**来指定格式

如果传过来的不跟指定格式相匹配则会报错,例如:

1
2
2022/9/8 -> yyyy-MM-dd
则会报错
1
2
3
4
5
6
@RequestMapping("/save")
@ResponseBody
public String save(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date){
System.out.println(date);
return "{'key': 'value'}";
}

类型与转换器

  • Converter接口

    1
    2
    3
    4
    public interface Converter<S, T> {
    @Nullable
    T Convert(S var1);
    }
    • 请求参数年龄数据(String -> Interger)
    • 日期格式转换(String -> Date)
  • @EnableWebMvc功能之一:根据类型匹配对应的类型转换器
    如果发现没有转换则将此开启即可

响应

  • 响应页面
  • 响应数据
    • 文本数据
    • json数据

响应页面

直接返回页面名称即可

1
2
3
4
5
6
7
8
@Controller
public class UserController {

@RequestMapping("/save")
public String save() {
return "index.jsp";
}
}

访问二级页面需要加上../页面:

1
http://localhost/user/save
1
2
3
4
5
6
7
8
9
@Controller
@RequestMapping("/user")
public class UserController {

@RequestMapping("/save")
public String save() {
return "../index.jsp";
}
}

响应数据

@ResponseBody响应方式自定义

返回的时候自动转Json

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

@RequestMapping("/save")
@ResponseBody
public User save() {
User user = new User();
user.setId(12);
user.setName("haog");
return user;
}
}
  • 类型:方法注解
  • 位置:SpringMVC控制器方法定义上方
  • 作用:设置当前控制器返回值作为响应体
  • 范例:如上所示

REST风格

REST简介

  • REST,表现形式状态转换

    • 传统风格资源描述形式

      1
      2
      http://localhost/user/getById?id=1
      http://localhost/user/saveUser
    • REST风格描述形式

      1
      2
      http://localhost/user/1
      http://localhost/user
  • 优点:

    • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
    • 书写简化
  • 按照REST风格访问资源时使用行为动作区分对资源进行了何种操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    http://localhost/users     // 查询全部用户信息  GET(查询)
    http://localhost/users/1 // 查询指定用户信息 GET(查询)
    http://localhost/users // 添加用户信息 POST(新增/保存)
    http://localhost/users // 修改用户信息 PUT(修改/更新)
    http://localhost/users/1 // 删除用户信息 DELETE(删除)

    注意事项:
    上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
    描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:
    users,books,accounts...
  • 根据REST风格对资源进行访问称为RESTful

案例

用@RequestMapping中的methoh

1
2
3
4
5
@RequestMapping(value = "/users", method = RequestMethod.POST)
@ResponseBody
public String save() {
return "success!";
}

传参需要设置路径变量**@PathVariable**同时需要在路径后面加上要传的参数,用{参数},名字要跟方法参数名一样

1
2
3
4
5
6
@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
@ResponseBody
public String save(@PathVariable Integer id) {
System.out.println(id);
return "success!";
}

快速开发

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

@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
@ResponseBody
public String save(@PathVariable Integer id) {
System.out.println(id);
return "success!";
}
}

可以将@ResponseBody和路径提取出来变成

1
2
3
4
5
6
7
8
9
10
11
@Controller
@ResponseBody
@RequestMapping("/users")
public class UserController {

@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String save(@PathVariable Integer id) {
System.out.println(id);
return "success!";
}
}

可以把@Controller和@ResponseBody结合变成**@RestController**

method可以变成**@(请求名)Mapping(路径)**

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/users")
public class UserController {

@PostMapping("/{id}")
public String save(@PathVariable Integer id) {
System.out.println(id);
return "success!";
}
}