Spring概念
- Spring是轻量级的开源JavaEE框架
- Spring可以解决企业应用开发的复杂性
- Spring两大核心部分:IOC和AOP
- IOC:控制反转,将对象创建过程交给Spring管理
- AOP:面向切面,不修改源代码进行功能增强
- Spring特点
- 方便解耦,简化开发
- 支持AOP
- 声明式事务
- 方便程序测试
- 方便集成其它框架
- 降低JavaEE API的使用难度

Spring简单使用
最基本的使用,需要导入的依赖,maven对应如下,其中包含了spring-aop、spring-beans、spring-core、spring-expression
1
2
3
4
5
6<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>编辑配置文件,文件名为任意值,如applicationConfig.xml
1
2
3
4
5
6
<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="userService" class="com.zephon.test.UserService"/>
</beans>读取配置文件并进行测试
# IOC1
2
3
4
5
6
7
8
public void test(){
// 1. 加载Spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationConfig.xml");
// 2. 获取配置创建的对象
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
IOC的作用与原理
作用:
- 控制反转,把对象创建和对象之间的调用过程交给Spring管理
- 降低耦合度
底层原理、BeanFactory:
- xml解析+工厂模式+反射
- BeanFactory:IOC容器的基本实现,是Spring内部的使用接口,不提供开发人员使用
- 加载配置文件时创建对象(版本Spring 5.3.22)
- ApplicationContext:BeanFactory接口的子接口,提供更多理强大的功能,一般由开发人员使用
- 加载配置文件时就会将配置文件中的对象创建(版本Spring 5.3.22)
- 详细可见之前的自己简单完成依赖注入功能
IOC管理Bean(基于XML)
创建对象
在Spring的xml配置文件中使用
标签并使用对应属性,常用的一些属性: - id:唯一标识
- class:对象对应的全限定类名
- name:与id类似,但可以使用特殊符号(使用较少)
默认会执行无参构造器创建对象
### Spring注入属性1
<bean id="userService" class="com.zephon.test.UserService"/>
DI:依赖注入,就是注入属性,注入方法包括:
- 使用set方法注入,对应的类中必须实现对应属性的set方法,然后在xml配置文件中使用
标签的子标签 ,内部属性包括: - name:属性名
- value:属性值
- 子标签:
:表示给属性设置空值 :可用来设置特殊符号,如< >就需要写成 <[CDATA[< >]]> 然后再使用测试方法测试获取Bean即可1
2
3
4<bean id="user" class="com.zephon.test.User">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
- 使用有参数构造器注入,使用
标签下的子标签 - name:属性名
- value:属性值
1
2
3
4<bean id="user" class="com.zephon.test.User">
<constructor-arg name="username" value="root"/>
<constructor-arg name="password" value="123456"/>
</bean>
- 使用set方法注入,对应的类中必须实现对应属性的set方法,然后在xml配置文件中使用
一种不常用简化方式:p名称空间注入
- 添加p名称空间,在beans标签属性中添加:xmlns:p="http://www.springframework.org/schema/p"
- 进行属性注入:
1
2
3
4
5
6
7
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.zephon.test.User" p:username="root" p:password="123456"/>
</beans>
注入其它类型属性
- 注入外部bean
例:service类调用dao类,必须在Service类中写有Dao对象对应的set方法 1
2
3
4<bean id="userService" class="com.zephon.test.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.zephon.test.dao.UserDaoImpl"/>
- 注入内部bean和级联赋值
- 一对多关系:如部门和员工
1
2
3
4
5
6
7
8
9<bean id="employee" class="com.zephon.test.Employee">
<property name="ename" value="张三"/>
<property name="dept">
<!-- 内部bean -->
<bean id="dept" class="com.zephon.test.Dept">
<property name="name" value="策划部"/>
</bean>
</property>
</bean>
- 一对多关系:如部门和员工
- 级联赋值
第一种写法
1
2
3
4
5
6
7
8<bean id="employee" class="com.zephon.test.Employee">
<property name="ename" value="张三"/>
<!-- 级联赋值 -->
<property name="dept" ref="dept"/>
</bean>
<bean id="dept" class="com.zephon.test.Dept">
<property name="name" value="财务部"/>
</bean>第二种写法
1
2
3
4
5
6
7
8<bean id="employee" class="com.zephon.test.Employee">
<property name="ename" value="张三"/>
<!-- 级联赋值 -->
<property name="dept" ref="dept"/>
<!-- 需要在Employee中写dept的get方法 -->
<property name="dept.name" value="财务部"/>
</bean>
<bean id="dept" class="com.zephon.test.Dept"/>
注入集合类型属性
注入数组
1
2
3
4
5
6
7
8<bean id="stu" class="com.zephon.test.Student">
<property name="courses">
<array>
<value>语文</value>
<value>数学</value>
</array>
</property>
</bean>注入List
1
2
3
4
5
6<property name="list">
<list>
<value>1</value>
<value>2</value>
</list>
</property>注入Map
1
2
3
4
5
6<property name="map">
<map>
<entry key="JAVA" value="java"/>
<entry key="PHP" value="php"/>
</map>
</property>集合中的数据是对象情况,则将上述value换成ref,然后与之前的bean一样处理即可
1
2
3
4
5
6
7<property name="list">
<list>
<ref bean="course1"/>
</list>
</property>
...
<bean id="course1" class="..."/>提取公共部分
- 在配置文件中引入名称空间util:xmlns:util="http://www.springframework.org/schema/util"
- 修改xsi:schemaLocation,添加utils对应部分
- 使用utils标签完成提取 ## IOC管理Bean(基于注解)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id="aList">
<!-- 如果是对象则用ref -->
<value>1</value>
<value>2</value>
</util:list>
<bean id="student" class="com.zephon.test.Student">
<property name="list" ref="aList"/>
</bean>
</beans>
使用注解的目的:简化XML配置
创建对象相关注解
四个常用注解功能一样,都可以用来创建bean实例
- @Component:普通注解
- value:和XML中的id一样,表示bean的唯一标识,可省略不写,默认值是类名首字母变小写后的结果
- @Service:通常用在业务层
- @Controller:通常用在控制层
- @Repository:通常用在持久层
使用方法:
如果没有aop依赖需要先引入aop依赖
开启组件扫描功能(同样需要引入context名称空间)
1
2
3
4
5
6
7
8
9
10
11
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启组件扫描 -->
<!-- 如果需要扫描多个包,多个包之间用逗号隔开;或者直接扫描包上层目录 -->
<context:component-scan base-package="com.zephon.test.dao,com.zephon.test.service"/>
</beans>创建类并在对应的类上面添加注解
1
2
3
4
public class UserService {
...
}
- 上述配置文件中,还可以自定义包扫描内容,如:
1
2
3
4
5
6
7
8
9
10
11<!-- use-default-filters:是否使用默认filter -->
<context:component-scan base-package="com.zephon.test" use-default-filters="false">
<!-- 表示只扫描注解为Controller的类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 或 -->
<context:component-scan base-package="com.zephon.test" use-default-filters="false">
<!-- 表示排除注解为Controller的类,扫描其它的类 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
属性注入相关的注解
- @Autowired:根据属性类型自动注入(装配)
- @Qualifier:根据属性名称自动注入(装配),需要和@Autowired一起使用(有时一个接口对应多个实现类,就需要再加上对应的名称)
- value:指定为创建对象是使用的id值
- @Resource:可以根据类型注入也可以根据名称注入
- name:设定值后则根据设定的名称进行注入,否则根据类型进行注入
- @Value:注入普通类型属性
- value:表示属性对应的值,如@Value(value = "root")表示给属性赋值为root
使用流程:
- 编写类,如UserService和UserDao
- 在被使用的各个类上加上注解,如在UserDao上加@Repository,在UserService上加@Service
- 在Service类上添加对应的属性并在属性上加注解,如@Autowired,不需要写对应的set方法 或:
1
2
3
4
5
6
7
8
9
public class UserService {
UserDao userDao;
public void add(){
userDao.add();
}
}1
2
3
4
5
6
7
8
9
10
public class UserService {
// -->对应的是UserDaoImpl上的@Repository(value = "userDao1")
UserDao userDao;
public void add(){
userDao.add();
}
}
完全注解开发
创建配置类,替代XML配置文件
1
2
3
4
public class SpringConfig {
}编写测试类
## Bean管理1
2
3ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
FactoryBean
- Spring有两种类型Bean,一种普通bean,另外一种是工程Bean(FactoryBean)
- 普通Bean:在配置文件中定义bean的类型就是返回类型
- 工厂Bean:在配置文件中定义bean的类型可以和返回类型不一样
如:
- 创建一个类实现FactoryBean接口,作为工程Bean
- 实现接口里面的方法,在里面可以定义返回的Bean类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class MyBean implements FactoryBean<User> {
// 定义返回的Bean
public User getObject() throws Exception {
User user = new User();
user.setUsername("root");
return user;
}
// 定义返回的Bean类型
public Class<?> getObjectType() {
return User.class;
}
}1
<bean id="myBean" class="com.zephon.test.MyBean"/>
1
2ApplicationContext context = new ClassPathXmlApplicationContext("applicationConfig.xml");
User user = context.getBean("myBean", User.class);
Bean的作用域
在Spring中可以设置创建的bean实例是单例还是多例
1
2<!-- singleton:单实例, prototype:多实例 -->
<bean id="user" class="com.zephon.test.User" scope="prototype"/>Spring中默认情况下创建的bean是单实例对象
singleton和prototype区别:
- singleton表示单实例,prototype表示多实例
- 设置scope时为singleton时,加载Spring配置文件时就会创建单实例对象;而设置为prototype则是在调用getBean()方法是创建对象
如果是在web工程则还可设置request和session ### Bean的生命周期
- 通过构造器创建Bean实例(无参构造)
- 为Bean的属性赋值(调用set方法)
- 将Bean实例传递给Bean后置处理器的postProcessBeforeInitialization方法(需要自定义类,实现BeanPostProcessor)
- 调用Bean的初始化方法(需要进行配置初始化的方法)
- 将Bean实例传递给Bean后置处理器postProcessAfterInitialization方法
- 使用bean(可获取状态)
- 当容器关闭时,调用bean的销毁方法(需要进行配置销毁的方法)(((ClassPathXmlApplicationContext)context).close();) > 测试输出: > 构造器调用 > 调用set方法 > 初始化前的后置处理器 > 初始化方法 > 初始化后的后置处理器 > 使用bean > 销毁方法
1
2
3
4
5<!-- 需要在User类中自己定义initMethod方法和destroyMethod方法 -->
<bean id="user" class="com.zephon.test.User" init-method="initMethod" destroy-method="destroyMethod"/>
<!-- 配置所有Bean的后置处理器 -->
<bean id="myBeanPost" class="com.zephon.test.Post"/>
XML自动装配(使用较少)
- 根据指定装配规则(属性名或属性类型),Spring自动将匹配的属性值进行注入,如:
1
2
3<!-- 也可以根据类型注入 byType -->
<bean id="user" class="com.zephon.test.User" autowire="byName"/>
<bean id="book" class="com.zephon.test.Book"/>
引入外部的属性文件
创建外部属性文件,properties格式文件(可以写数据库相关信息等),如:
1
2
3
4com.mysql.cj.jdbc.Driver =
jdbc:mysql://localhost:3306/userDB =
root =
123456 =引入到Spring的xml配置文件
- 引入context名称空间,xmlns:context="http://www.springframework.org/schema/context"
- 修改xsi:schemaLoaction
- 导入properties文件
- 配置属性
1
2
3
4
5
6
7
8
9
10
11
12
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="..." class="...">
<property name="driverClassName" value="${prop.driverClass}"/>
......
</bean>
</beans>
AOP
概念:面向切面编程(Aspect Oriented Programming),可能用来对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,提高开发效率。
AOP底层原理:使用动态代理
- 有接口的情况,使用JDK动态代理(源于接口实现)
- 被代理类实现了某个接口A
- 创建接口A实现类代理对象,然后通过代理对象增强被代理类的功能
- 具体实现:使用Proxy类中对应的方法
1
2
3
4
5
6
7
8Object obj = new Object(); // 被代理类
Object proxyInstance = Proxy.newProxyInstance(obj.getClass().getClassLoader(), new Class[]{A.class}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强方法");
return method.invoke(obj, args);
}
}); // 代理实例
- 没有接口的情况,使用CGLIB动态代理(源于继承)
- 被代理类为B
- 创建类B的子类的代理对象,增强被代理类的功能
AOP常用词
- 连接点:被增强的部分(方法),类中哪些方法能被增强,这些方法就是连接点
- 切入点:实际被增强的方法被称为切入点
- 通知(增强):实际增强的逻辑部分称为通知(原代码之外自己新增的部分)
- 通知有多种类型:
- 前置通知:在方法执行之前执行
- 后置通知:在方法执行之后执行
- 环绕通知:在方法执行前后均执行
- 异常通知:在方法抛出异常时执行
- 最终通知:无论如何都执行
- 通知有多种类型:
- 切面:将通知应用到切入点的过程称为切面
AOP使用
Spring框架一般基于AspectJ实现AOP操作
- AspectJ本身不是Spring组成部分,而是独立的AOP框架,只是一般为方便起见将AspectJ和Spring框架一起使用进行AOP操作
导入包
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.2</version>
</dependency>切入点表达式:
- 作用:表示对哪个类的哪个方法进行增强
- 语句结构:execution([权限修饰符] [返回类型] [全限定类名].[方法名称] ([参数列表]))
- 可使用*****表示任意类型
- 方法返回类型可省略
- 参数列表可以用..表示 ### 基于XML配置文件实现
创建被增强类和增强类和方法
1
2
3
4
5
6
7
8
9
10
11
12
13// User类文件
public class User {
public void add(){
System.out.println("被增强方法");
}
}
// UserProxy类文件
public class UserProxy {
public void before(){
System.out.println("before");
}
}在Spring配置文件中创建两个类对象(
) 在Spring配置文件中配置切入点
### 基于注解方式实现(常用)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="user" class="com.zephon.test.aop.User"/>
<bean id="userProxy" class="com.zephon.test.aop.UserProxy"/>
<!-- 配置AOP增强 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="point" expression="execution(* com.zephon.test.aop.User.add(..))"/>
<!-- 配置切面 -->
<aop:aspect ref="userProxy">
<!-- 增强作用的方法 -->
<aop:before method="before" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>创建被增强类,在类里定义方法
1
2
3
4
5
6
public class User {
public void add(){
System.out.println("被增强方法");
}
}创建增强类,编写增强逻辑
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
// 表示增强类优先级,当有多个增强类时,
// 按优先级执行,数字越小,优先级越高
public class UserProxy {
// 相同切入点抽取
public void point(){
}
// 前置通知
public void before(){
System.out.println("before");
}
public void after(){
System.out.println("after");
}
public void afterReturning(){
System.out.println("after returning");
}
public void afterThrowing(){
System.out.println("after throwing");
}
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕之前");
pjp.proceed();
System.out.println("环绕之后");
}
}在增强类中使用不同的方法代表不同的通知类型
进行通知的配置
- 在Spring配置文件中开启注解扫描或通过配置文件开启注解扫描
- 使用注解创建对象(即在两个类上添加注解@Component或其它)
- 在增强类上添加注解@Aspect
- 在Spring配置文件中开启生成代理对象(需要引入aop名称空间)
- 配置不同类型的通知
1
2
3
4
5
6
7
8
9
10
11
12
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.zephon.test.aop"/>
<!-- 开启AspectJ生成代理对象 -->
<aop:aspectj-autoproxy/>
</beans>
JDBCTemplate
JDBCTemplate:是Spring框架对JDBC进行封装,使用JDBCTemplate方便实现对数据库操作
导包
1 | <dependency> |
配置
1 |
|
一个例子: 1
2
3
4
5
6
7
8
9
10
11
public class UserDao {
JdbcTemplate jdbcTemplate;
public void addUser(User user) {
String sql = "insert into user (name,password,address,phone) values(?,?,?,?)";
int update = jdbcTemplate.update(sql, user.getName(), user.getPassword(), user.getAddress(), user.getPhone());
System.out.println(update);
}
}
- 增:
- update(String sql, Object... args)
- sql:SQL语句
- args:可变参数,设置SQL语句值
- 返回值:影响的行数
- batchUpdate(String sql, List<Object[]> batchArgs):批量添加
- update(String sql, Object... args)
- 删:
- update(String sql, Object... args)
- sql:SQL语句
- args:可变参数,设置SQL语句值
- 返回值:影响的行数
- batchUpdate(String sql, List<Object[]> batchArgs):批量删除
- update(String sql, Object... args)
- 改:
- update(String sql, Object... args)
- sql:SQL语句
- args:可变参数,设置SQL语句值
- 返回值:影响的行数
- batchUpdate(String sql, List<Object[]> batchArgs):批量修改
- update(String sql, Object... args)
- 查询:
- queryForObject(String sql, Class requiredType)
- sql:SQL语句
- requiredType:返回类型
- 为Integer.class时
- 返回某一个值(如查询数量(select count(*)))
- 为Integer.class时
- queryForObject(String sql, RowMapper rowMapper, Object... args)
- sql:SQL语句
- rowMapper:RowMapper接口,针对SQL查询返回不同类型的数据,RowMapper接口里面实现完成数据封装,如:new BeanPropertyRowMapper
(User.class) - args:SQL语句的填充参数
- 返回指定类型的单个对象
- query(String sql, RowMapper rowMapper, Object... args)
- sql:SQL语句
- rowMapper:针对不同类型进行封装
- args:SQL语句的填充参数
- 返回List类型的集合数据
- queryForObject(String sql, Class requiredType)
事务管理
- Spring使用的是声明式事务管理,底层使用AOP原理
Spring事务管理API
- 接口PlatformTransactionManager:表示事务管理器,这个接口针对不同的框架提供不同的实现类,如JDBC使用的DataSourceTransactionManager
声明式事务管理使用
基于注解方式
- 在Spring配置文件中配置事务管理器
- 在Spring配置文件中开启事务注解(需要先引入tx名称空间)
- 在Service类其中的需要的方法上面添加事务注解@Transactional,表示这个类中所有方法或指定的方法添加事务,包含可选参数:
- propagation:事务传播行为
- REQUIRED:如果有事务在运行,当前方法就在这个事务内运行,否则就启动一个新的事务,并在自己的事务内运行
- REQUIRED_NEW:当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起
- SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
- NOT_SUPPORTES:当前方法不应该运行在事务中,如果有运行的事务,将它挂起
- MANDATORY:当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
- NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
- NESTED:如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行
- isolation:事务隔离级别
- READ UNCOMMITTED:读未提交
- READ COMMITTED:读已提交
- REPEATABLE:可重复读
- SERIALIZABLE:可串行化
- timeout:超时时间(单位是秒)
- 设置事务必须在指定时间内进行提交,超时了则进行回滚
- 默认值是-1,表示不设超时
- readOnly:是否只读
- 默认是false,设置为true则只能进行查询,不能进行修改、删除、插入
- rollbackFor:回滚
- 设置出现哪些异常时进行事务回滚
- noRollbackFor:不回滚
- 设置出现哪些异常时不进行事务回滚 ### 基于XML配置文件方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
- 设置出现哪些异常时不进行事务回滚
- propagation:事务传播行为
- 配置事务管理器
- 配置通知
- 配置切入点和切面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置通知 -->
<tx:advice id="txAdvice">
<!-- 配置事务参数 -->
<tx:attributes>
<!-- 指定在哪种规则的方法上添加事务:方法名或通配符 -->
<tx:method name="account*" isolation="READ_COMMITTED" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切入点和切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="point" expression="execution(* com.zephon.test.UserService(..))"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="point"/>
</aop:config>
完全注解
- 创建配置类
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
// 开启事务
public class SpringConfig {
// 创建数据库连接池
public DruidDataSource getDruidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.jdbc.cj.jdbc.driver");
druidDataSource.setUrl("jdbc:mysql:///jdbc_learn");
druidDataSource.setUsername("root");
druidDataSource.setPassword("123456");
return druidDataSource;
}
// 创建JdbcTemplate对象
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器对象
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
Spring5新功能
整个Spring5框架代码基于JDK8,运行时兼容JDK9,将许多不建议的类和方法在代码库中删除
Spring5框架自带了通用的日志封装(可整合log4j2)
@Nullable注解:
- 用在方法上,表示方法返回可以为空
- 用在属性或参数值上,表示属性或参数值可以为空
函数式注册对象GenericApplicationContext,可以用来将自己new的对象注册到Spring中,例:
1
2
3
4
5// 1. 创建GenericApplicationContext对象
GenericApplicationContext genericApplicationContext = new GenericApplicationContext();
// 2. 调用registerBean注册,注册之前清空
genericApplicationContext.refresh();
genericApplicationContext.registerBean("user", User.class, () -> new User());整合JUnit5单元测试框架
SpringWebFlux
SpringWebFlux(简单介绍)
介绍
- 是Spring5添加的新模块,用于web开发的,功能与Spring MVC类似的,Webflux是因为当前一种比较流行的响应式编程而出现的框架
- 使用传统web框架,如Spring MVC,是基于Servlet的,而Webflux是一种异步非阻塞框架,而异步非阻塞是在Servlet3.1以后才支持的,核心是基于Reactor的相关API实现的
- 什么是异步非阻塞?(解释正确性待验证)
- 异步和同步:针对调用者
- 同步:调用者发送请求后需要等到回应才做其它事情
- 异步:调用者改善请求后不用等到回应就能做其它事情
- 阻塞和非阻塞:针对被调用者
- 阻塞:被调用者收到请求后完成请求任务才给出反馈
- 非阻塞:被调用者收到请求后马上给出反馈再完成请求任务
- 异步和同步:针对调用者
- Webflux特点:
- 异步非阻塞:在有限资源下提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程
- 函数式编程:Spring5就是基于Java8,Webflux使用Java8函数式编程实现路由请求
- 和SpringMVC比较:
- 两个构架都可以使用注解方式,都运行在Tomacat等容器中
- SpringMVC采用命令式编程,WebFlux采用异步响应式编程
响应式编程
响应式编程是一种面向数据流和变化传播的编程范式,意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
Java中提供了观察者模式相关的类(Observer和Observerable)
例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class ObserverDemo extends Observable {
public static void main(String[] args) {
// 被观察者
ObserverDemo observable = new ObserverDemo();
// 添加观察者
observable.addObserver(new Observer() {
public void update(Observable o, Object arg) {
System.out.println("发生了变化");
}
});
observable.addObserver((o, arg)->{
System.out.println("收到被观察者通知,准备改变");
});
// 被观察者数据变化
observable.setChanged();
// 通知观察者
observable.notifyObservers();
}
}Reactor中使用的实际上是JDK9中新出的Flow.java文件对应的发布订阅函数式接口
Reactor实现响应式编程:
- 两个核心类:
- Mono:实现Publisher接口,实现发布者,返回0个或1个元素
- Flux:实现Publisher接口,实现发布者,返回N个元素
- Flux和Mono都是数据流的发布者,可以发出三种数据信号:
- 元素值
- 错误信号,是终止信号,终止数据流同时把错误信息传递给订阅者
- 完成信号,是终止信号,用于告诉订阅者数据流已经结束
- API
- just(Object... args):直接声明
- fromArray(Object[] objArray):通过数组声明
- fromIterable(List<> list):通过列表声明
- fromStream(Stream<> stream):通过流声明
- 这些方法都只是声明数据流,但没有发出数据流,只有进行订阅之后都会触发数据流,如:
- 两个核心类:
Flux.just(1, 2, 3, 4).subscribe(System.out::println);
- 三种信号特点:
- 错误信号和完成信号都是终止信号,不能共存
- 如果没有发送任何元素,而是直接发送错误信号或完成信号,表示是空数据流
- 如果没有错误信号,也没有完成信号,表示是无限数据流
- 操作符:
- 对数据流进行一个个操作,就是操作符,就和工厂流水线类似
- 常用操作符:
- map:元素映射为新元素
- flatMap :元素映射为流
Webflux执行流程和核心API
- 核心控制器DispatchHandler,实现接口WebHandler
- HandlerMapping:请求查询到处理的方法
- HandlerAdapter:真正负责请求处理
- HandlerResultHandler:响应结果处理
- 执行流程暂无
- SpringWebflux实现函数式编程,两个接口
- RouterFunction:路由功能,将请求转发给对应的Handler
- HandlerFunction:处理请求并做出响应
基于注解编程模型
创建SpringBoot工程,引入webflux依赖
1
2
3
4
5
6
7
8<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency> -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>创建实体类、服务层和控制层
创建接口:
1
2
3
4
5public interface UserService {
Mono<User> getUserById(Integer id);
Flux<User> getAllUsers();
Mono<Void> saveUserInfo(Mono<User> user);
}创建实现类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class UserServiceImpl implements UserService{
// 创建Map集合模拟数据库
private final Map<Integer, User> users = new HashMap<>();
public UserServiceImpl(){
this.users.put(1, new User(1, "张三", "男", 20));
this.users.put(2, new User(2, "李四", "女", 22));
this.users.put(3, new User(3, "王明", "男", 18));
}
public Mono<User> getUserById(Integer id) {
return Mono.justOrEmpty(this.users.get(id));
}
public Flux<User> getAllUsers() {
return Flux.fromIterable(this.users.values());
}
public Mono<Void> saveUserInfo(Mono<User> userMono) {
return userMono.doOnNext(user -> {
this.users.put(user.getId(), user);
}).thenEmpty(Mono.empty());
}
}创建Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserController {
private UserService userService;
public Mono<User> getUserById(int id) {
return userService.getUserById(id);
}
public Flux<User> getAllUsers(){
return userService.getAllUsers();
}
public Mono<Void> saveUser( User user){
Mono<User> userMono = Mono.just(user);
return userService.saveUserInfo(userMono);
}
}
- 说明:
- SpringMVC,基于SpringMVC+Servlet+Tomcat
- SpringWebflux,基于SpringWebflux+Reactor+Netty
基于函数式编程模型
需要自己初始化服务器
SpringWebflux请求和响应不再是ServletRequest和ServletResponse,而是ServerRequest和ServerResponse
创建Handler
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
28public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
public Mono<ServerResponse> getUserById(ServerRequest request) {
Integer id = Integer.valueOf(request.pathVariable("id"));
// 空值处理
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<User> userMono = userService.getUserById(id);
// 将userMono转换返回
return userMono.flatMap(user -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON).
bodyValue(user)).switchIfEmpty(notFound);
}
public Mono<ServerResponse> getAllUsers(ServerRequest request){
Flux<User> allUsers = this.userService.getAllUsers();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(allUsers, User.class);
}
public Mono<ServerResponse> save(ServerRequest request){
Mono<User> userMono = request.bodyToMono(User.class);
return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
}
}编写Router
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
30public class Server {
// 创建路由
public RouterFunction<ServerResponse> routingFunction(){
UserHandler userHandler = new UserHandler(new UserServiceImpl());
return RouterFunctions.route(
GET("/user/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandler::getUserById)
.andRoute(GET("/user").and(accept(MediaType.APPLICATION_JSON)), userHandler::getAllUsers)
.andRoute(POST("/save").and(accept(MediaType.APPLICATION_JSON)), userHandler::save);
}
// 创建服务器
public void createReactorServer(){
RouterFunction<ServerResponse> routerFunction = routingFunction();
HttpHandler httpHandler = toHttpHandler(routerFunction);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
// 创建服务器
HttpServer httpServer = HttpServer.create();
httpServer.handle(adapter).bindNow();
}
public static void main(String[] args) {
// 测试
Server server = new Server();
server.createReactorServer();
System.out.println("Enter to exit");
System.in.read();
}
}使用WebClient调用替换浏览器测试
# Spring配置常用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class Client {
public static void main(String[] args) {
// 传入调用服务器地址
WebClient webClient = WebClient.create("http://localhost:56284");
// 根据id查询
int id = 1;
User userResult = webClient.get().uri("/user/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(User.class)
.block();
System.out.println(userResult);
Flux<User> userFlux = webClient.get().uri("/user")
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToFlux(User.class);
userFlux.map(User::getUsername)
.buffer().doOnNext(System.out::println).blockFirst();
}
}
XML中的bean标签
- id:唯一标识
- class:对象对应的全限定类名
- 子标签:
:对象通过setter方法设置内部的属性,可设置属性 - name:属性名
- value:属性值
- ref:引用的其它bean的id值
- scope:作用域,默认为singleton表示单实例,设置为prototype表示多实例
- init-method:配置初始化方法,值为要执行的方法名
- destroy-method:配置销毁方法,值为要执行的方法名
- autowire:自动装配,常用的两个值:
- byName:根据属性名称注入,bean的Id值要和类中定义的属性名一样
- byType:根据属性类型注入,但此时相同类型的bean不能有多个
- 子标签:
:对象通过构造器参数设置对象内部属性,可设置属性: - name:属性名,对应的是构造器上的形参名
- value:属性值,和
中的一样 - ref:引用的其它bean的id值
- index:表示有参构造器中的第几个参数(从0开始),不常用
XML中的context命名空间
引入context命名空间的方法
1
2
3
4
5
6
7
<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命名空间常用的标签
- <context:property-placeholder>:引入外部属性文件
- 属性location:表示外部属性文件对应的位置,如"classpath:jdbc.properties"表示类路径下的jdbc.properties文件,引入的属性可以通过${属性名}进行调用
- <context:component-scan>:用于开启组件扫描,通常在使用注解管理Bean时使用
- 属性base-package:表示要扫描的包,如有多个值则可以用逗号隔开
- 属性use-default-filters:表示是否使用默认Filter,默认值是true,如果需要自定义filter规则时需要改为false
- 子标签<context:include-filter>:设置要扫描哪些内容
- type:表示针对的类型
- expression:表示具体类型对应的全限定类名
- 子标签<context:exclude-filter>:设置不扫描的内容
- type:表示类型
- expression:表达式
- <context:property-placeholder>:引入外部属性文件
XML中的aop命名空间
引入aop命名空间
1
2
3
4
5
6
7
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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><aop:aspectj-autoproxy>:开启AspectJ生成代理对象
<aop:config>:配置aop增强
- 子标签<aop:pointcut>:配置切入点
- 属性id:唯一标识
- 属性expression:切入点表达式
- 子标签<aop:aspect>:配置切面
- 属性ref:增强类对应的bean
- 子标签<aop:before>:配置前置通知
- 属性method:前置通知对应在增强类中的方法名
- 属性pointcut-ref:切点引用
- 子标签<aop:after>等,同上
- 子标签<aop:pointcut>:配置切入点
XML中的tx命名空间
引入tx命名空间
1
2
3
4
5
6
7
8
9
10
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"><tx:annotation-driven>:开启事务注解
- 属性transaction-manager:指定配置的事务管理器id
常用注解
创建对象
四者的使用方法是一样的
- @Component:普通组件Bean
- value:和XML中bean的id一样,表示类的唯一标识,默认值为首字母小写后的类名
- @Service:常用于服务层的类上
- @Repository:常用于持久层
- @Controller:学用于控制层
属性注入
- @Autowired:根据属性类型自动注入(装配)
- @Qualifier:根据属性名称自动注入(装配),需要和@Autowired一起使用
- value:值为创建对象的指定的value,表示属性名称
- @Resource:可以根据类型注入也可以根据名称注入
- name:指定具体值后,则表示根据对应名称进行注入
- @Value:注入普通类型属性
配置相关
- @Configuration:用在类上表示这是一个配置类,用于替代XML配置文件
- @ComponentScan:表示开启注解扫描
- basePackages={"包名"}:指定要开启的包
- @Bean:用在方法上表示一个Bean对象,方法的返回值为对象的类型,需要设置的值作为参数传入方法中
- 例:
1
2
3
4
5
6
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
AOP相关
- @Aspect:用在类上表示该类是一个用于增强的类
- @Order:用在类上表示优先级,当有多个增强类时,按优先级执行,数字越小,优先级越高
- @EnableAspectJAutoProxy:用在配置类上表示开启对注解AOP的支持
- proxyTargetClass:为true时表示强制使用cglib代理方式
- @Before:用在方法上表示该方法是前置通知
- value:切入点表达式表示要增强的方法,切入点表达式:
- 语句结构:execution([权限修饰符] [返回类型] [全限定类名].[方法名称] ([参数列表]))
- 可使用*****表示任意类型
- 方法返回类型可省略
- 参数列表可以用..表示
- 语句结构:execution([权限修饰符] [返回类型] [全限定类名].[方法名称] ([参数列表]))
- value:切入点表达式表示要增强的方法,切入点表达式:
- @After:用在方法上表示该方法是后置通知,发生异常的情况下也会执行
- @AfterReturning:用在方法上表示该方法是在被增强方法返回结果之后才执行,即必须没有发生异常
- @AfterThrowing:用在方法上表示该方法是异常通知
- @Around:用在方法上表示该方法是环绕通知,对应的方法上可以添加形参ProceddingJoinPoint pjp,然后在方法中调用pjp.proceed()表示执行被增强的方法,当发生异常时不执行环绕之后的内容
- @Pointcut:用在方法上将相同切入点进行抽取,后续再需要对应的表达式时直接使用对应的方法名即可
- 执行的顺序(Spring 5.3.22版本) > 环绕之前 > before > 被增强方法 > after returning > after > 环绕之后
事务管理
- @Transactional:用在类上表示类中所有方法都支持事务;用在方法上表示指定方法支持事务
- 参数propagation:事务传播行为
- REQUIRED:如果有事务在运行,当前方法就在这个事务内运行,否则就启动一个新的事务,并在自己的事务内运行
- REQUIRED_NEW:当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起
- SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
- NOT_SUPPORTES:当前方法不应该运行在事务中,如果有运行的事务,将它挂起
- MANDATORY:当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
- NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
- NESTED:如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行
- 参数isolation:事务隔离级别
- READ UNCOMMITTED:读未提交
- READ COMMITTED:读已提交
- REPEATABLE:可重复读
- SERIALIZABLE:可串行化
- 参数timeout:超时时间(单位是秒)
- 设置事务必须在指定时间内进行提交,超时了则进行回滚
- 默认值是-1,表示不设超时
- 参数readOnly:是否只读
- 默认是false,设置为true则只能进行查询,不能进行修改、删除、插入
- 参数rollbackFor:回滚
- 设置出现哪些异常时进行事务回滚
- 参数noRollbackFor:不回滚
- 设置出现哪些异常时不进行事务回滚
- 参数propagation:事务传播行为
测试相关
- @RunWith(SpringJUnit4ClassRunner.class):Junit4单元测试框架
- @ContextConfiguration("classpath:bean.xml"):加载配置文件
- @ExtendWith(SpringExtension.class):Junit5单元测试框架
- @SpringJunitConfig:复合注解,即:@SpringJunitConfig(locations="classpath:bean.xml")相当于@ExtendWith注解和@ContextConfiguration一起使用