Spring5知识回顾及查漏补缺

基本概念

  • Spring是一个轻量级的开源的JavaEE框架

  • Srping可以解决企业应用开发的复杂性

  • Spring有两个核心部分:IOC和AOP

    • IOC:控制反转,把创建对象的过程交给Spring进行管理
    • AOP:面向切面
  • Spring特点:

    • 方便解耦,简化开发
    • AOP编程支持
    • 方便程序测试
    • 方便集成其他框架
    • 降低JavaEE API开发使用难度
    • 方便进行事务操作

IOC容器

底层原理

用到xml解析、工厂模式、反射技术

IOC容器实现方式

IOC思想基于IOC容器完成,IOC容器底层就是对象工厂

Spring提供IOC容器实现两种方式:BeanFactory、ApplicationContext

BeanFactory

IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用

特点:

加载配置文件时不会创建对象,在使用对象时才创建对象

ApplicationContext

BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用

特点:

加载配置文件时就会将配置文件中的对象进行创建

ApplicationContext接口的主要实现类有:

  • FileSystemXmlApplicationContext
  • ClassPathXmlApplicationContext

什么是Bean管理

  • Spring创建对象
  • Spring注入属性

基于xml管理bean

方式一

无参构造+属性set方式创建:

1
2
3
4
5
6
7
8
9
<bean id="user" class="com.zhuweitung.spring5.mvc.model.User">
<property name="name" value="wangwu"></property>
<property name="age">
<null/>
</property>
<property name="age">
<value><![CDATA[<<文字>>]]></value>
</property>
</bean>

默认执行无参构造来创建对象

方式二

有参构造创建:

1
2
3
<bean id="user" class="com.zhuweitung.spring5.mvc.model.User">
<constructor-arg name="name" value="lisi"></constructor-arg>
</bean>
常用标签及属性解释
  • bean:指一个对象
    • id:唯一标识
    • class:类全路径
    • scope:作用域
      • singleton:单实例,默认值,spring加载配置文件时就会创建对象
      • prototype:多实例,加载配置文件时不创建对象,调用getBean时创建对象
  • property:指对象的属性
    • name:属性名
    • value:属性值
    • ref:关联其他bean
  • null:设置属性值为null
  • constructor-arg:指类的构造器
    • name:构造器中参数名
    • value:参数值
属性值包含特殊字符解决方式
  • 将特殊字符转义
  • 使用<![CDATA[]]>包裹属性值
自动装配

根据指定装配规则(属性名或者属性类型),不需要明确指定属性名或属性类型,Spring自动将匹配的属性值注入;

使用到bean标签的autowire属性

  • byName:根据属性名称注入
  • byType:根据属性类型注入

FactoryBean

Spring有两种bean,一种普通bean,另外一种工厂bean(FactoryBean)

  • 普通bean:在配置文件中定义的bean类型就是返回类型
  • 工厂bean:在配置文件中定义bean类型可以和返回类型不一样,实现FactoryBean接口

Bean生命周期

  • 通过构造器创建bean实例(无参构造)
  • 为bean的属性设置值和对其他bean引用(调用set方法)
  • 把bean实例传递给bean的后置处理器方法(BeanPostProcessor接口的postProcessBeforeInitialization)
  • 调用bean的初始化方法(需要进行配置)
  • 把bean实例传递给bean的后置处理器方法(BeanPostProcessor接口的postProcessAfterInitialization)
  • bean可以使用了
  • 当容器关闭时,调用bean的销毁方法(需要进行配置销毁的方法)

基于注解管理bean

需要引入spring-aop依赖,开启组件扫描(配置文件或注解方式)

配置文件方式开启组件扫描:

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

注解方式开启组件扫描:

1
2
3
4
@Configuration  // 作为配置类,替代xml配置文件
@ComponentScan(basePackages = "com.zhuweitung") // 开启组件扫描
public class SpringConfig {
}
对象创建
  • @Component:一般用在组件
  • @Service:一般用在service层
  • @Controller:一般用在controller层
  • @Repository:一般用在dao层

上面四个注解功能是一样的,都可以用来创建Bean实例

属性注入
  • @Autowired:根据属性类型自动装配
  • @Qualifier:根据属性名称进行注入,需要和@Autowired一起使用,当接口有多个实现类时用此注解指定实现类名称
  • @Resource:可以根据类型注入,也可以根据名称注入,它是javax包中的
  • @Value:注入普通类型属性

AOP

底层原理

底层使用动态代理,分为两种情况:

  • 有接口时,使用JDK动态代理(创建接口实现类的代理对象)

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

    public static void main(String[] args) {
    ICalculate o = (ICalculate) getProxyInstance(new PlusCalculate());
    System.out.println(o.calculate(1, 2));
    // invoke执行前...
    // calculate方法执行...
    // invoke执行后...
    // 3
    }

    public static Object getProxyInstance(Object target) {
    return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CalculateInvocationHandler(target));
    }

    private static class CalculateInvocationHandler implements InvocationHandler {
    private Object target;

    public CalculateInvocationHandler(Object target) {
    this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("invoke执行前...");
    Object o = method.invoke(target, args);
    System.out.println("invoke执行后...");
    return o;
    }
    }
    }

    interface ICalculate {
    int calculate(int a, int b);
    }

    class PlusCalculate implements ICalculate {
    @Override
    public int calculate(int a, int b) {
    System.out.println("calculate方法执行...");
    return a + b;
    }
    }
  • 没接口时,使用CGLIB动态代理(创建当前类子类的代理对象)

术语

  • 连接点(Join point):类中可以增强的方法
  • 切点(Pointcut):通过切点表达式匹配接入点
  • 通知(增强,Advice):实际增强的逻辑部分
    • 前置通知:切入方法执行前做通知,@Before
    • 后置通知:切入方法执行后做通知,@AfterReturning
    • 环绕通知:切入方法执行前后做通知,@Around
    • 异常通知:切入方法执行抛异常做通知,@AfterThrowing
    • 最终通知:切入方法执行不管有无异常都做通知(类似finally),@After
  • 切面(Aspect):由一系列切点、增强和引入组成的模块对象,可定义优先级,从而影响增强和引入的执行顺序

切入点表达式

execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])

AspectJ注解方式

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
@Aspect
@Component
public class CalculateEnhance {

// 前置通知
@Before(value = "execution(* com.zhuweitung.spring5.aop.ICalculate.calculate(..))")
public void beforeCalculate() {
System.out.println("beforeCalculate...");
}

// 后置通知
@AfterReturning(value = "execution(* com.zhuweitung.spring5.aop.ICalculate.calculate(..))")
public void afterReturningCalculate() {
System.out.println("afterReturningCalculate...");
}

// 环绕通知
@Around(value = "execution(* com.zhuweitung.spring5.aop.ICalculate.calculate(..))")
public Object aroundCalculate(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("aroundBeforeCalculate...");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("aroundAfterCalculate...");
return proceed;
}

// 异常通知
@AfterThrowing(value = "execution(* com.zhuweitung.spring5.aop.ICalculate.calculate(..))")
public void afterThrowingCalculate() {
System.out.println("afterThrowingCalculate...");
}

// 最终通知
@After(value = "execution(* com.zhuweitung.spring5.aop.ICalculate.calculate(..))")
public void afterCalculate() {
System.out.println("afterCalculate...");
}

}

执行结果:

1
2
3
4
5
6
//aroundBeforeCalculate...
//beforeCalculate...
//calculate方法执行...
//afterReturningCalculate...
//afterCalculate...
//aroundAfterCalculate...
其他注意事项

将可以重复使用的表达式提取出来,放到切入点,如下:

1
2
3
4
5
6
7
8
9
// 公共接入点
@Pointcut(value = "execution(* com.zhuweitung.spring5.aop.ICalculate.calculate(..))")
public void pointcut() {}

// 前置通知
@Before(value = "pointcut()")
public void beforeCalculate() {
System.out.println("beforeCalculate...");
}

当一个方法被多个增强类切入时,可以在增强了上加上@Order注解来设置优先级

AspectJ配置文件方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 配置bean -->
<bean id="plusCalculate" class="com.zhuweitung.spring5.aop.PlusCalculate"></bean>
<bean id="calculateEnhance" class="com.zhuweitung.spring5.aop.CalculateEnhance"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.zhuweitung.spring5.aop.ICalculate.calculate(..))"/>
<!-- 切面 -->
<aop:aspect ref="calculateEnhance">
<!-- 前置通知 -->
<aop:before method="beforeCalculate" pointcut-ref="pointcut"></aop:before>
<!-- 后置通知 -->
<aop:after-returning method="afterReturningCalculate" pointcut-ref="pointcut"></aop:after-returning>
<!-- 环绕通知 -->
<aop:around method="aroundCalculate" pointcut-ref="pointcut"></aop:around>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowingCalculate" pointcut-ref="pointcut"></aop:after-throwing>
<!-- 最终通知 -->
<aop:after method="afterCalculate" pointcut-ref="pointcut"></aop:after>
</aop:aspect>
</aop:config>

JdbcTemplate

Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作

JdbcTemplate对象的创建和属性注入

使用注解方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public DataSource hikariDataSource() {
Properties properties = new Properties();
properties.put("dataSourceClassName", dataSourceClassName);
properties.put("dataSource.jdbcUrl", jdbcUrl);
properties.put("dataSource.username", username);
properties.put("dataSource.password", password);
properties.put("minimumIdle", minimumIdle);
properties.put("maximumPoolSize", maximumPoolSize);
return new HikariDataSource(new HikariConfig(properties));
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource hikariDataSource) {
return new JdbcTemplate(hikariDataSource);
}

在dao层引入并使用

1
2
3
4
5
6
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int add(User user) {
return jdbcTemplate.update("INSERT INTO `user` (`name`, `password`) VALUES (?, ?)", user.getName(), user.getPassword());
}

常用方法

  • update(sql, args…):执行增删改操作

  • queryForObject(sql, class, args…):查询返回单列,如count查询

    • class未返回单列的类型
  • queryForObject(sql, rowmapper, args…):查询返回单个对象

    • 第一个参数是sql语句
    • 第二参数是RowMapper接口实现类的实例
    1
    jdbcTemplate.queryForObject("select * from user where id = ?", new BeanPropertyRowMapper<User>(User.class), id);
    • 第三个参数是sql语句值,是可变参数列表
  • query(sql, rowmapper, args…):查询多个对象的集合

    • 第一个参数是sql语句
    • 第二个参数是RowMapper接口实现类的实例
    1
    jdbcTemplate.query("select * from user where name like concat('%', ? '%')", new BeanPropertyRowMapper<User>(User.class), name);
    • 第三个参数是sql语句值,是可变参数列表
  • batchUpdate(sql, list):批量增删改

    • 第二个参数 List<Object[]> batchArgs ,每个Object[]表示添加一行数据的sql语句值
    1
    2
    3
    4
    5
    6
    7
    public int[] batchAdd(List<User> users) {
    List<Object[]> batchArgs = new ArrayList<>();
    users.forEach(u -> {
    batchArgs.add(new Object[]{u.getName(), u.getPassword()});
    });
    return jdbcTemplate.batchUpdate("INSERT INTO user (`name`, `password`) VALUES (?, ?)", batchArgs);
    }

事务管理

Spring进行事务管理有两种方式:

  • 编程式事务管理:使用比较麻烦
  • 声明式事务管理:使用方便
    • 基于注解方式
    • 基于xml配置文件方式

Spring进行声明式事务管理,底层使用AOP原理

Spring事务管理API

PlatformTransactionManager接口针对不同的框架有不同的实现类

使用方式

Spring配置类上加上@EnableTransactionManagement注解开启事务

继续在配置类中创建事务管理器对象实例

1
2
3
4
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource hikariDataSource) {
return new DataSourceTransactionManager(hikariDataSource);
}

在需要事务操作的方法上加上@Transactional注解

1
2
3
@Transactional(rollbackFor = Exception.class)
public boolean doSomething(Object... args) {
}

声明式事务管理参数配置

即@Transactional的属性

  • propagation:事务传播行为,描述当一个事务方法被另一个事务方法调用时,这个事务该如何进行
    • REQUIRED:若有事务在运行当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
    • SUPPORTS:若有事务正在运行,当前方法就在这个事务内运行,否则他可以不允许在事务中
    • MANDATORY:当前方法必须运行在事务内部,若没有正在运行的事务,就抛出异常
    • REQUIRES_NEW:当前方法必须启动新事务,并在自己的事务内运行,若有其他事务正在运行,应该将它挂起
    • NOT_SUPPORTED:当前方法不应该运行在事务中,若有运行的事务,将它挂起
    • NEVER:当前方法不应该运行在事务中,若有运行的事务,就抛出异常
    • NESTED:若有事务在运行,当前方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行
  • isolation:配置事务隔离级别,三个读问题:脏读、不可重复度、幻读
    • DEFAULT:数据库默认隔离级别
    • READ_UNCOMMITTED:读未提交,会出现脏读、不可重复度、幻读
    • READ_COMMITTED:读已提交,会出现不可重复度、幻读
    • REPEATABLE_READ:可重复读,特殊情况下会出现幻读
    • SERIALIZABLE:可串行化
  • timeout:超时时间,以秒为单位(默认为-1,表示不使用),事务需要在一定时间内提交,否则会回滚
  • readOnly:是否只读,开启后只能查询,不能增删改
  • rollbackFor:回滚,表示出现哪些异常后进行回滚
  • noRollbackFor:不回滚,表示出现哪些异常后不进行回滚

Spring5新特性

  • 自带了通用的日志封装

  • 核心容器支持@Nullable注解

    • 可以使用在方法、属性、参数上面,表示方法返回、属性值、参数值可以为空
  • 核心容器支持函数式风格

  • 支持整合JUnit5、JUnit4

    • JUnit4单元测试加上下面的注解
    1
    2
    3
    4
    5
    6
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)

    @Test // org.junit.Test
    public void test() {
    }
    • JUnit5单元测试加上下面的注解
    1
    2
    3
    4
    5
    6
    7
    //@ExtendWith(SpringExtension.class)
    //@ContextConfiguration(classes = SpringConfig.class)
    @SpringJUnitConfig(SpringConfig.class) // 可以用这一个注解代替上面两个注解

    @Test // org.junit.jupiter.api.Test
    public void test() {
    }

SpringWebFlux

基本概念

Spring5添加的新模块,用于web开发,功能与SpingMVC类似,WebFlux是使用响应式编程的一种框架;

SpringMVC是基于Servlet容器的框架,而WebFlux是一种异步非阻塞的框架(Servlet3.1之后才支持异步非阻塞),核心是基于Reactor的相关API实现的

异步非阻塞

异步和同步是针对调用者,调用者发送请求后,不等待回应就去进行其他操作为异步,反之为同步

非阻塞和阻塞是针对被调用者,被调用者收到请求后,马上做出反馈再去执行操作为非阻塞,反之为阻塞

特点
  • 非阻塞式:在有限的资源下,提高系统吞吐量和伸缩性,基于Reactor实现响应式编程
  • 函数式编程:WebFlux使用函数式编程实现路由请求
与SpingMVC比较

Spring MVC or WebFlux?

  • 都可以使用注解
  • 都可以运行在tomcat
  • SpingMVC采用命令式编程
  • WebFlux采用异步响应式编程

响应式编程

响应式编程是一种面向数据流和变化传播的编程范式,这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

如,excel中公式“=B1+C1”,包含该公式的单元格值会随着其他单元格值的变化而变化。

观察者模式类

Java8及其之前版本提供Observer接口和Observable类;

实现Observer接口的是观察者,监听被观察者的变化;

继承Observable类的类是可观察的,即被观察者;

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ObserverDemo extends Observable {
public static void main(String[] args) {
ObserverDemo observer = new ObserverDemo();
// 添加观察者
observer.addObserver((o, arg) -> {
System.out.println("观察者1号观察到变化...");
});
observer.addObserver((o, arg) -> {
System.out.println("观察者2号观察到变化...");
});
// 设置发生改变
observer.setChanged();
// 通知观察者
observer.notifyObservers();
}
}

Java9使用Flow类替代观察者模式

响应式(Reactor)编程实现

两个核心类,Mono和Flux,这两个类实现Publisher接口,提供丰富操作符;

Flux对象实现发布者,可以返回N个元素,Mono对象实现发布者,返回0或1个元素;

Mono和Flux的特点
  • 都可以发出三种数据信号:元素值、错误信号、完成信号

  • 错误信号和完成信号都是终止信号,告诉订阅者数据流结束了,错误信号还会吧错误信息传递给订阅者

  • 若没有发送任何元素值,而是直接发送终止信号,表示是空数据流

  • 若没有终止信号,表示是无线数据流

使用示例
1
2
3
4
5
6
7
8
// 调用just方法直接声明,调用subscribe方法进行订阅,触发数据流
Flux.just(1, 2, 3, 4).subscribe(System.out::println);
Mono.just(1).subscribe(System.out::println);
// 其他方法
Integer[] arr = {1, 2, 3, 4};
Flux.fromArray(arr);
Flux.fromIterable(Arrays.asList(arr));
Flux.fromStream(Arrays.stream(arr));
常用操作符
  • map:将元素映射为新元素
  • flatMap:将元素映射为流

执行流程和核心API

SpringWebFlux基于Reactor,默认使用容器是Netty,Netty是高性能的NIO(异步非阻塞)框架

执行过程相关类

核心控制器DispatcherHandler,实现接口WebHandler

  • DispatcherHandler:负责请求的处理

  • HandlerMapping:负责查询处理请求的方法

  • HandlerAdapter:负责实现请求处理

  • HandlerResultHandler:负责对响应结果的处理

实现函数式编程相关类
  • RouterFunction接口:路由处理
  • HandlerFunction接口:处理函数

基于注解编程模型

与SpringMVC差不多,也使用@Controller、@Service、@Repository

基于函数式编程模型

与基于注解编程模型的区别主要在控制层(对外提供接口)的实现上不一样

围绕两个核心接口:RouterFunction(实现路由功能,将请求转发给对应的handler)和HandlerFunction(处理请求生成响应的函数);

请求和响应不再是ServletRequest和ServletResponse,而是ServerRequest和ServerResponse


Spring5知识回顾及查漏补缺
https://blog.kedr.cc/posts/2217974762/
作者
zhuweitung
发布于
2021年1月8日
许可协议