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 管理操作
Bean 管理方式
基于配置
创建对象
1
| <bean id="user" class="com.cyan.User"></bean>
|
在 Spring 配置文件中,使用 bean 标签,添加对应属性,即可创建对象
常见属性
创建对象时,默认使用无参构造方法完成对象创建
如果要使用有参构造有三种方式
下标赋值
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方法
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:component-scan base-package="com.cyan"></context:component-scan>
|
example 1
1 2 3 4 5 6 7 8
|
<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
|
<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; }
@Repository public class UserDaoImpl implements UserDao{ }
|
@Qualifier
1 2 3 4 5 6 7 8 9 10 11 12
| @Service public class UserService { @Autowired @Qualifier(value="userDaoImpl") private UserDao userDao; }
@Repository(value="userDaoImpl") public class UserDaoImpl implements UserDao{ }
|
@Resource
1 2 3 4 5 6 7 8 9 10 11
| @Service public class UserService { @Resource private UserDao userDao; } @Repository(value="userDaoImpl") public class UserDaoImpl implements UserDao{ }
|
1 2 3 4 5 6 7 8 9 10 11
| @Service public class UserService { @Resource(value="userDaoImpl") private UserDao userDao; } @Repository(value="userDaoImpl") public class UserDaoImpl implements UserDao{ }
|
@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">
<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">
<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
|
<bean id="userService" class="com.cyan.service.UserService">
<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 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 { private String[] courses; private List<String> lists; private Map<String, String> maps; private Set<String> sets; private List<Course> courseList; ... }
|
数组类型属性注入
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"> <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"> <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"> <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"> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> </bean>
<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
| <util:list id="bookList"> <value>var1</value> <value>var2</value> </util: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
| <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
| <property name="hobbies"> <list> <value>SING</value> <value>CODE</value> <value>SLEEP</value> </list> </property>
|
Map 注入
1 2 3 4 5 6
| <property name="card"> <map> <entry key="Card" value="14725869"/> </map> </property>
|
Set 注入
1 2 3 4 5 6
| <property name="games"> <set> <value>LOL</value> </set> </property>
|
NULL 注入
1 2 3 4
| <property name="wife"> <null/> </property>
|
Properties 注入
1 2 3 4 5 6
| <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类型 可以和返回类型不一样
创建类,让该类作为工厂 bean,实现接口 FactoryBean
实现接口的方法,在实现的方法中 定义返回的 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 { public Object getObject() throws Exception { Course course = new Course(); course.setCname("Java"); return course; } public Class<?> getObjectType() { } public boolean isSingleton() { } }
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 装配属性
装配方式
xml 配置
java 配置
隐式自动装配 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>
|
byName[属性名称]
byType[属性类型]
注解实现自动装配
1.导入命名空间 context
2.开启注解支持
如果@Autowired自动装配,无法通过一个注解完成,可以使用@Qualifier(value=””)配合使用
引入外部属性文件
直接配置数据库信息
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 框架
在项目工程引入 AOP 依赖
切入点表达式:切入点表达式作用:知道对哪个类里面的方法进行增强
语法结构:
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: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() { } }
|
编写代理类[不同方法代表不同通知类型]
1 2 3 4 5 6
| public class UserProxy { public void before() { } }
|
配置通知[注意:要引入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
| <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 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
| <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次读出来的 记录数不一样
- 对于前者, 只需要锁住 满足条件的记录。 对于后者, 要锁住 满足条件及其相近的记录。
隔离级别 |
含义 |
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; } @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplat(); 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 |
设置出现哪些异常不进行事务回滚 |