Spring

Spring官方文档请点我

Spring 是轻量级的开源的 JavaEE 框架,用于解决企业应用开发的复杂性,其核心为 IOC 和 AOP

  • Spring 是开源的、轻量级免费框架
  • 控制反转(IOC)和面向切面编程(AOP)
    • IOC 控制反转:把创建对象过程交给 Spring 管理
    • AOP 面向切面:不修改源代码进行功能增强
  • 支持事务处理,对框架整合的支持

Spring 特点

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

Spring三核心

  • 控制反转 IOC
  • 依赖注入 DI
  • 面向切面编程 AOP

IOC

基本概念

IOC 概念

  • 控制反转,把 对象创建和对象之间的调用过程,交给 Spring 管理
  • 目的:降低耦合度

底层原理

原始方式

工厂模式

IOC 过程

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

Spring 提供 IOC 容器实现两种方式:(两个接口)

  • BeanFactory :IOC 容器基本实现,是 Spring 内部的使用接口
    • 加载配置文件时不会创建对象,在使用对象时才创建
  • ApplicationContext :BeanFactory 的子接口,提供更强大的功能
    • 加载配置文件时就创建对象

IOC 操作

Bean 管理

Bean 管理操作

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

Bean 管理方式

  • 基于配置文件
  • 基于注解

基于配置

创建对象

1
<bean id="user" class="com.cyan.User"></bean>

在 Spring 配置文件中,使用 bean 标签,添加对应属性,即可创建对象

常见属性

属性 说明
id 唯一标识
class 类全路径

创建对象时,默认使用无参构造方法完成对象创建

如果要使用有参构造有三种方式

下标赋值

1
2
3
4
<!--有参构造 下标赋值-->
<bean id="userIndex" class="com.cyan.pojo.User">
<constructor-arg index="0" value="index"/>
</bean>

参数类型

1
2
3
4
<!--有参构造 参数类型 不建议使用-->
<bean id="userType" class="com.cyan.pojo.User">
<constructor-arg type="java.lang.String" value="Type"/>
</bean>

参数名

1
2
3
4
<!--有参构造 参数名-->
<bean id="userName" class="com.cyan.pojo.User">
<constructor-arg name="name" value="name"/>
</bean>

注入属性

DI:依赖注入,即注入属性

  • 使用 set方法 注入
  • 使用 有参构造方法 注入

set方法

1
2
3
<bean id="address" class="com.cyan.pojo.Address">
<property name="address" value="南通"/>
</bean>

有参构造方法

创建类,定义属性,创建属性对应的构造方法,然后在Spring配置文件中配置

1
2
3
4
5
6
7
8
9
public class Orders {
private String oname;
private String address;

public Orders(String oname, String address) {
this.oname = oname;
this.address = address;
}
}
1
2
3
4
<bean id="oders" class="com.cyan.pojo.Oders">
<constructor-arg name="oname" value="电脑"></constructor-arg>
<constructor-arg name="address" value="China"></constructor-arg>
</bean>

基于注解

注解概念

  • 注解:代码特殊标记,格式,@注解名称(属性名称=属性值,…)
  • 使用注解,注解可以作用在类上面,方法上面,属性上面
  • 使用目的:简化 xml 配置

创建对象

引入 spring-aop 依赖并且开启组件扫描

1
2
3
4
5
<!--引入context命名空间-->

<!--开启组件扫描-->
<!--如果扫描多个包,使用逗号隔开;扫描包上层目录-->
<context:component-scan base-package="com.cyan"></context:component-scan>

example 1

1
2
3
4
5
6
7
8
<!--example 1
use-default-filters="false" 表示现在不使用默认filter,自己配置filter
context:include-filter,设置扫描的内容
-->
<context:component-scan base-package="com.cyan" use-default-fliters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

example 2

1
2
3
4
5
6
7
8
<!--example 2
下面配置扫描包所有内容
context:exclude-filter:设置不扫描的内容
-->
<context:component-scan base-package="com.cyan">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

注入属性

注解 说明
@Autowired 根据属性类型进行自动装配
@Qualifier 根据属性名称进行注入[@Autowired和@Qualifier一起使用]
@Resource 可以根据类型注入,可以根据名称注入
@Value 注入普通类型属性

@Resource 和 @Autowired 区别

  • 都用来自动装配,放在属性字段上
  • @Autowired 通过 byType 实现,要求对象存在
  • @Resource 通过 byName 实现,若找不到名字,通过 byType 实现

@Autowired

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserService {
@Autowired
private UserDao userDao;
// method
}

@Repository
public class UserDaoImpl implements UserDao{
// method
}

@Qualifier

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class UserService {
@Autowired
@Qualifier(value="userDaoImpl")
private UserDao userDao;
// method
}

@Repository(value="userDaoImpl")
public class UserDaoImpl implements UserDao{
// method
}

@Resource

1
2
3
4
5
6
7
8
9
10
11
// 根据类型注入
@Service
public class UserService {
@Resource
private UserDao userDao;
// method
}
@Repository(value="userDaoImpl")
public class UserDaoImpl implements UserDao{
// method
}
1
2
3
4
5
6
7
8
9
10
11
// 根据名称注入
@Service
public class UserService {
@Resource(value="userDaoImpl")
private UserDao userDao;
// method
}
@Repository(value="userDaoImpl")
public class UserDaoImpl implements UserDao{
// method
}

@Value

1
2
@Value(value="var")
private String name;

c 命名空间注入

1
2
3
4
5
6
7
8
9
10
11
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<!--c命名空间注入,通过构造器注入,constructor-->
<bean id="user2" class="com.cyan.pojo.User" c:age="18" c:name="CName"/>
</beans>

p 命名空间注入

1
2
3
4
5
6
7
8
9
10
11
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<!--p命名空间注入可以直接注入属性值 property-->
<bean id="user" class="com.cyan.pojo.User" p:name="PName" p:age="18"/>
</beans>

注入外部 Bean

1
2
3
4
5
6
7
8
9
10
public class UserService {
private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
...
}
}
1
2
3
4
5
6
7
8
9
10
<!--Service和Dao对象创建-->

<bean id="userService" class="com.cyan.service.UserService">
<!--注入userDao对象
name:类里面属性名称
ref:创建userDao对象bean标签的id值
-->
<property name="userDao" ref="userDaoImpl">
</bean>
<bean id="userDaoImpl" class="com.cyan.dao.UserDaoImpl"></bean>

注入内部 Bean 和级联赋值

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 Dept {
private String dname;

public void setDname(String dname) {
this.dname = dname;
}
}

public class Emp {
private String ename;
private String gender;
private Dept dept;

public void setEname(String ename) {
this.ename = ename;
}

public void setGender(String gender) {
this.gender = gender;
}

public void setDept(Dept dept) {
this.dept = dept;
}
}

注入内部 Bean

1
2
3
4
5
6
7
8
9
10
11
12
<!--内部bean-->
<bean id="emp" class="com.cyan.bean.Emp">
<!--设置普通属性-->
<property name="ename" value="cyan"></property>
<property name="gender" value="男"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.cyan.bean.Dept">
<property name="dname" value="销售部"></property>
</bean>
</property>
</bean>

级联赋值

1
2
3
4
5
6
7
8
9
10
11
<!--级联赋值-->
<bean id="emp" class="com.cyan.bean.Emp">
<!--设置普通属性-->
<property name="ename" value="cyan"></property>
<property name="gender" value="男"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.cyan.bean.Dept">
<property name="dname" value="销售部"></property>
</bean>

注入集合属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Student {
// 1. 数组类型属性
private String[] courses;

// 2. list集合类型属性
private List<String> lists;

// 3. map集合类型属性
private Map<String, String> maps;

// 4. set集合类型
private Set<String> sets;

// 5. 对象集合
private List<Course> courseList;

// Set 方法
...
}

数组类型属性注入

1
2
3
4
5
6
7
8
9
10
<!--集合类型属性注入-->
<bean id="student" class="com.cyan.pojo.Student">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>Java</value>
<value>Spring</value>
</array>
</property>
</bean>

list 类型属性注入

1
2
3
4
5
6
7
8
9
10
<!--集合类型属性注入-->
<bean id="student" class="com.cyan.pojo.Student">
<!--list类型属性注入-->
<property name="lists">
<list>
<value>Spring MVC</value>
<value>SpringBoot</value>
</list>
</property>
</bean>

map 类型属性注入

1
2
3
4
5
6
7
8
9
10
<!--集合类型属性注入-->
<bean id="student" class="com.cyan.pojo.Student">
<!--map类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="SPRING" value="spring"></entry>
</map>
</property>
</bean>

set 类型属性注入

1
2
3
4
5
6
7
8
9
10
<!--集合类型属性注入-->
<bean id="student" class="com.cyan.pojo.Student">
<!--set类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>

list 对象集合类型属性注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--集合类型属性注入-->
<bean id="student" class="com.cyan.pojo.Student">
<!--注入list集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>

<!--创建多个Course对象-->
<bean id="course1" class="com.cyan.pojo.Course">
<property name="cname" value="Spring 5 Framework"></property>
</bean>

<bean id="course2" class="com.cyan.pojo.Course">
<property name="cname" value="Mybatis Framework"></property>
</bean>

提取集合注入部分

Spring 配置文件引入命名空间 util

1
2
3
xmlns:c="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/util
https://www.springframework.org/schema/util/spring-util.xsd"

使用 util 标签完成 list 集合注入

1
2
3
4
5
6
7
8
9
10
<!--1.提取list集合类型属性注入-->
<util:list id="bookList">
<value>var1</value>
<value>var2</value>
</util:list>

<!--2.提取list集合类型属性注入使用-->
<bean id="book" class="com.cyan.pojo.Book">
<property name="list" ref="bookList"></property>
</bean>

注入总结

普通值注入

1
2
<!--普通值注入-->
<property name="name" value="Cyan"/>

注入 ref

1
2
<!--Bean注入 ref-->
<property name="address" ref="address"/>

Array 注入

1
2
3
4
5
6
7
8
<!--数组注入-->
<property name="books">
<array>
<value>JAVA</value>
<value>C++</value>
<value>Python</value>
</array>
</property>

List 注入

1
2
3
4
5
6
7
8
<!--List注入-->
<property name="hobbies">
<list>
<value>SING</value>
<value>CODE</value>
<value>SLEEP</value>
</list>
</property>

Map 注入

1
2
3
4
5
6
<!--Map注入-->
<property name="card">
<map>
<entry key="Card" value="14725869"/>
</map>
</property>

Set 注入

1
2
3
4
5
6
<!--Set注入-->
<property name="games">
<set>
<value>LOL</value>
</set>
</property>

NULL 注入

1
2
3
4
<!--NULL注入-->
<property name="wife">
<null/>
</property>

Properties 注入

1
2
3
4
5
6
<!--Properties注入-->
<property name="info">
<props>
<prop key="Sno">123456</prop>
</props>
</property>

属性值包含特殊符号

1
2
3
4
<!--属性值包含特殊符号-->
<property name="address">
<value>![CDATA[《南京》]]</value>
</property>

Bean 类型

Spring 有两种类型 bean,一种普通 bean,另一种 FactoryBean

普通 bean

在配置文件中定义 bean 类型就是 返回类型

FactoryBean

在配置文件定义 bea n类型 可以和返回类型不一样

  1. 创建类,让该类作为工厂 bean,实现接口 FactoryBean

  2. 实现接口的方法,在实现的方法中 定义返回的 bean 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建类
public class MyBean implements FactoryBean {
// 定义返回bean
public Object getObject() throws Exception {
Course course = new Course();
course.setCname("Java");
return course;
}
public Class<?> getObjectType() {

}
public boolean isSingleton() {

}
}

// test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
1
2
<bean id="myBean" class="com.cyan.factorybean.MyBean">
</bean>

Bean 作用域

在 Spring 里面,默认情况下,bean 是单实例

如何设置创建的 Bean 属于单实例还是多实例

在 spring 配置文件 bean 标签里面有 属性scope [scope用于设置单实例还是多实例]

  • singleton,表示单实例对象,默认值
  • prototype,表示多实例对象

singleton 和 prototype 区别

  • scope 域中设置 singleton 表示单实例,设置 prototype 表示多实例
  • scope 域中设置值是 singleton,加载 spring 配置文件时 就会 创建单实例对象
  • scope 域中设置值是 prototype,不是在加载 spring 配置文件时创建对象,而是在调用 getBean 方法时 创建多实例对象

Bean 生命周期

生命周期

生命周期表示从对象创建到对象销毁的过程。

bean 生命周期

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

Bean 自动装配

自动装配

根据指定装配规则(属性名称或属性类型),Spring 自动 将匹配的 属性值注入

  • 自动装配是 Spring 满足 bean 依赖的一种方式
  • Spring 会在上下文自动寻找,并自动给 bean 装配属性

装配方式

  1. xml 配置

  2. java 配置

  3. 隐式自动装配 bean

    1
    2
    3
    4
    5
    <bean id="cat" class="com.cyan.pojo.Cat"/>
    <bean id="dog" class="com.cyan.pojo.Dog"/>
    <bean id="person" class="com.cyan.pojo.Person" autowire="byName">
    <property name="name" value="Name"/>
    </bean>
  4. byName[属性名称]

  5. byType[属性类型]

注解实现自动装配

1.导入命名空间 context

2.开启注解支持

如果@Autowired自动装配,无法通过一个注解完成,可以使用@Qualifier(value=””)配合使用

引入外部属性文件

直接配置数据库信息

  • 导入 Druid 包
  • 配置连接池
1
2
3
4
5
6
<bean id="dataSource" class="com.alibaba.druid.pool.DriudDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc://localhost:3306/user_db"></property>
<property name="username" value="****"></property>
<property name="password" value="****"></property>
</bean>

引入外部属性文件配置数据库

  • 编写 properties 文件
  • 将 properties 文件引入 spring 配置文件,同时引用 context 命名空间
1
2
3
4
driverClass=com.mysql.jdbc.Driver
url=jdbc://localhost:3306/user_db
username=xxxx
password=xxxx
1
2
3
4
5
6
7
8
9
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DriudDataSource">
<property name="driverClassName" value="${driverClass}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</bean>

AOP

基本概念

AOP 是面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。通俗来讲:不通过修改源代码方式,在主干功能里面添加新功能。

底层原理

AOP 底层使用 动态代理

动态代理使用情况

  • 有接口情况,默认使用 JDK 动态代理

    创建接口实现类代理对象,增强类的方法

  • 没有接口情况,使用 CDLIB 动态代理

    【创建子类的代理对象,增强类的方法】

AOP (JDK动态代理

参数 说明
ClassLoader loader 类加载器
类<?>[] interfaces 增加方法所在的类,这类实现的接口,支持多个接口
InvocationHandler h 实现该接口InvocationHandler,创建代理对象,写增强的方法
方法 说明
// java.lang.reflect.Proxy
static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

案例

1
2
3
4
5
6
// 创建接口
public interface UserDao {
public int add(int a,int b);

public String update(String id);
}
1
2
3
4
5
6
7
8
9
// 实现接口
public class UserDaoImpl implements UserDao {
public int add(int a, int b) {
return a+b;
}
public String update(String id) {
return id;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 创建代理类
public class JDKProxy {
public void test() {
Class[] interfaces = (UserDao.class);
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader, interfaces, new UserDaoProxy(userDao));
int result = dao.add(1,2);
System.out.println("result:"+result);
}
// 创建代理对象
class UserDaoProxy implements InvocationHandler {
// 传递要代理的对象
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法前
System.out.println("在方法前执行"+method.getName()+":传递的参数"+Arrays.toString(args));
// 执行被增强的方法
Object res = method.invoke(obj,args);
// 方法后
System.out.println("在方法后执行"+obj);
return res;
}
}
}

相关术语

术语 说明
连接点 类里面被增强的方法
切入点 实际被真正增强的方法
通知 实际增强的逻辑部分
切面 动作,把通知应用到切入点过程

Spring AOP 中,通过 Advice 定义横切逻辑,Spring 中支持 5 种类型的Advice:

通知类型 连接点 实现接口
前置通知 方法前 org.springframework.aop.MethodBeforeAdvice
后置通知 方法后 org.springframework.aop.AfterReturningAdvice
环绕通知 方法前后 org.aopalliance.intercept.MethodInterceptor
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvice
引介通知 类中增加新的方法属性 org.springframework.aop.IntroductionInterceptor

即 Aop 在不改变原有代码的情况下,去增加新的功能

相关操作

Spring 框架一般是 基于AspectJ实现 AOP 操作,AspectJ 不是 Spring 组成部分,独立 AOP 框架

  1. 在项目工程引入 AOP 依赖

  2. 切入点表达式:切入点表达式作用:知道对哪个类里面的方法进行增强

语法结构:

1
2
3
4
5
6
7
8
execution([权限修饰符][返回类型][类全路径][方法名称][参数列表])

1.对com.cyan.dao.BookDao类的add进行增强
execution(* com.cyan.dao.BookDao.add(..))
2.对com.cyan.dao.BookDao类的所有方法增强
execution(* com.cyan.dao.BookDao.*(..))
2.对com.cyan.dao包里所有类的所有方法增强
execution(* com.cyan.dao.*.*(..))

配置实现

创建代理类和被代理类

1
2
3
4
5
6
7
8
9
10
11
public class Book {
public void buy() {

}
}

public class BookProxy {
public void before() {
System.out.println("Before");
}
}

在配置文件中创建两个类对象

1
2
3
<!--创建对象-->
<bean id="book" class="com.cyan.Book"></bean>
<bean id="bookProxy" class="com.cyan.BookProxy"></bean>

在配置文件中配置切入点

1
2
3
4
5
6
7
<!--配置AOP增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.cyan.Book.buy(..))"/>
<!--配置切面-->
<aop:before method="before" pointcut-ref="p"/>
</aop:config>

注解实现

创建被代理类并定义方法

1
2
3
4
5
public class User {
public void add() {
// method
}
}

编写代理类[不同方法代表不同通知类型]

1
2
3
4
5
6
public class UserProxy {
// 前置通知
public void before() {
//method
}
}

配置通知[注意:要引入aop命名空间]

开启注解扫描

1
2
<!--开启注解扫描-->
<context:component-scan base-package="com.cyan"></context:component-scan>

创建User和UserProxy对象

1
2
3
4
5
@Component
public class User {

@Component
public class UserProxy {

在代理类上面添加注解@Aspect

1
2
3
@Component    
@Aspect
public class UserProxy {

在配置文件中开启生成代理对象

1
2
<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

配置不同类型通知

在代理类里面,在作为通知方法上面添加通知类型注解,使用切入点表达式

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
@Component    
@Aspect
public class UserProxy {
// 前置通知
@Before(value="execution(* com.cyan.User.add(..))")
public void before() {
System.out.print("Before method")
}
// 后置通知
@After(value="execution(* com.cyan.User.add(..))")
public void after() {
System.out.print("After method")
}
// 异常通知
@AfterThrowing(value="execution(* com.cyan.User.add(..))")
public void afterThrowing() {
System.out.print("AfterThrowing method")
}
// 环绕通知
@Around(value="execution(* com.cyan.User.add(..))")
public void around(ProceedingJoinPoint pj) throws Throwable{
System.out.print("Before around method");
pj.proceed();
System.out.print("After around method");
}

@AfterReturning(value="execution(* com.cyan.User.add(..))")
public void afterReturning() {
System.out.print("AfterReturning method")
}
}

测试

1
2
3
4
5
public void test() {
ApplicaiotnContext context = new ClassPathXmlApplicationContext("application-context.xml");
User user = context.getBean("user",User.class);
user.add();
}

完全注解开发

1
2
3
4
5
@Configuration
@ComponentScan(basePackages={"com.cyan"})
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AopConfig {
}

注意事项

相同的切入点抽取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component    
@Aspect
public class UserProxy {
// 相同切入点抽取
@Pointcut(value="execution(* com.cyan.User.add(..))")
public void pointcut() {

}
// 前置通知
@Before(value="pointcut()")
public void before() {
System.out.print("Before method")
}
}

有多个增强类对同一个方法增强,优先级设置

在增强类上添加注解 @Order(数字类型值)数字类型值越小优先级越高

1
2
3
4
5
6
7
8
9
@Component    
@Aspect
@@Order(1)
public class UserProxy {

@Component
@Aspect
@@Order(2)
public class PersonProxy {

实战演练

引入依赖

1
2
3
4
5
6
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
<scope>runtime</scope>
</dependency>

使用 Spring 的API

BeforeLog

1
2
3
4
5
6
public class BeforeLog implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) {
System.out.println(target.getClass().getName() + "'s " + method.getName() + "Invoked");
}
}

AfterLog

1
2
3
4
5
6
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) {
System.out.println("Invoked " + method.getName() + "Return " + returnValue);
}
}

约束

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">

</beans>

注册 bean

1
2
3
4
<!--注册bean-->
<bean id="userService" class="com.cyan.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.cyan.log.BeforeLog"/>
<bean id="afterLog" class="com.cyan.log.AfterLog"/>

使用 Spring API

1
2
3
4
5
6
7
8
9
<!--使用API接口-->
<!--配置AOP-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.cyan.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>

使用自定义 AOP[切面定义]

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--自定义类-->
<bean id="appPointcut" class="com.cyan.aop.AppPointcut"/>

<aop:config>
<!--自定义切面-->
<aop:aspect ref="appPointcut">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.cyan.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>

使用注解实现 AOP

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

@Before("execution(* com.cyan.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("方法执行前");
}

@After("execution(* com.cyan.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("方法执行后");
}

// 循环增强中,给定一个参数,代表获取处理切入的点
@Around("execution(* com.cyan.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("环绕前");

// 执行方法
Object proceed = pj.proceed();
System.out.println("环绕后");
}
}

事务

基本概念

事务

事务是数据库操作的 最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;

ACID 特性

特性 说明
原子性(Atomicity) 事务中所有操作是不可分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
一致性(Consistency) 事务执行后,数据库状态与其它业务规则保持一致。如转正业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。
隔离性(Isolation) 隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
持久性(Durability) 一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须你能保证通过某种机制恢复数据。

传播行为

事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播

传播属性 描述
REQUIRED 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
REQUIRED_NEW 当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行.否则它可以不运行在事务中
NOT_SUPPORTEI 当前的方法不应该运行在事务中。如果有运行的事务,将它挂起
MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。否则,就启动一个新的事务,并在它自己的事务内运行.

隔离级别

隔离级别定义了一个事务可能受其他并发事务的程度。

脏读(Dirty reads)

脏读发生在一个事务读取了另一个事务 改写但尚未提交的数据 时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。

不可重复读(Nonrepeatable read)

不可重复读发生在一个事务 执行相同的查询两次或两次以上,但是每次都得到不同的数据 时。这通常是因为 另一个并发事务在两次查询期间进行了更新

幻读(Phantom read)

幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着 另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现 多了一些原本不存在的记录

不可重复读与幻读的区别

  1. 不可重复读的重点是 修改:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
  2. 幻读的重点在于 新增或者删除:同样的条件, 第1次和第2次读出来的 记录数不一样
  3. 对于前者, 只需要锁住 满足条件的记录。 对于后者, 要锁住 满足条件及其相近的记录
隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的
脏读 不可重复读 幻读
READ UNCOMMITTED( 读未提交 )
READ COMMITTED( 读已提交 )
REPEATABLE READ(可重复读 )
SERIALIZABLE(串行化)

声明式事务

在 Spring 进行事务操作

  • 编程式事务管理
  • 声明式事务管理
    • 基于注解
    • 基于配置

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

基于注解

配置事务管理器

1
2
3
4
5
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>

开启事务注解

在 spring 配置文件中,引入tx命名空间

1
2
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

Service 类上添加事务注解

1
2
3
@Service
@Transactional
public class UserService{

完全注解开发:创建配置类代替配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@ComponentScan(basePackages={"com.cyan"})
@EnableTransactionManagement
public class TxConfig() {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("");
dataSource.setUrl("");
dataSource.setUsername("");
dataSource.setPassword("");
return dataSource;
}

// 创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplat();
// 注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}

基于配置

配置事务管理器

1
2
3
4
5
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>

配置通知

1
2
3
4
5
6
7
8
<!--配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="account" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

配置切入点和切面

1
2
3
4
5
6
7
<!--配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.cyan.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

参数配置

1
2
3
@Service
@Transaction(popagation=Propagation.REQUIRED,isolation=Isolation.REPEATABLE_READ)
public void UserService{
参数 说明
propagation 事务传播行为
isolation 事务隔离级别
timeout 超时时间,事务需要在一定时间内提交,如果不提交进行回滚,默认值是-1,设置时间以秒单位进行计算
readOnly 是否只读(读:查询操作,写:增删改),readOnly默认值false,表示可以增删改查,设置readOnly值为true,只能查询
rollbackFor 回滚,设置出现哪些异常进行事务回滚
noRollbackFor 设置出现哪些异常不进行事务回滚