Spring
IOC
iocIoc - Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想,将创建对象的权利交给spring,让spring去管理对象生命周期。在传统javase中由对象内部创建依懒所需对象,如所依懒对象构建过于复杂,代码将会变得相当臃肿,IOC呢?在IOC中有专门一个容器来初始化对象,控制对象的创建,获取等!来以此有效解耦程序所依懒的复杂对象构建!spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring我是什么,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。类的创建,销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。如下图所示
image.png
示列
1:xml配置
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- 若没写id,则默认为com.test.Man#0,#0为一个计数形式 -->
<bean id="man" class="com.test.Man"></bean>
</beans>
public class Test {
public static void main(String[] args) {
//加载项目中的spring配置文件到容器
//ApplicationContext context = new ClassPathXmlApplicationContext("resouces/applicationContext.xml");
//加载系统盘中的配置文件到容器
ApplicationContext context = new FileSystemXmlApplicationContext("E:/Spring/applicationContext.xml");
//从容器中获取对象实例
Man man = context.getBean(Man.class);
man.driveCar();
}
}
2:通过java注解的方式将配置加载到IOC容器
//同xml一样描述bean以及bean之间的依赖关系
@Configuration
public class ManConfig {
@Bean
public Man man() {
return new Man(car());
}
@Bean
public Car car() {
return new QQCar();
}
}
3:隐式的bean发现机制和自动装配
/**
* 这是一个游戏光盘的实现
*/
//这个简单的注解表明该类回作为组件类,并告知Spring要为这个创建bean。
@Component
public class GameDisc implements Disc{
@Override
public void play() {
System.out.println("我是马里奥游戏光盘。");
}
}
不过,组件扫描默认是不启用的。我们还需要显示配置一下Spring,从而命令它去寻找@Component注解的类,并为其创建bean。
@Configuration
@ComponentScan
public class DiscConfig {
}
DI(依赖注入)
Ioc重点是如何在系统运行中动态向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。理解DI的关键是:依赖,为什么需要依赖,谁注入谁,注入了什么,应用程序依赖于IoC容器,为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源比如对数据库操作的对象,IoC容器注入这个,应用程序依赖这个对象,IoC和DI有什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:依赖注入,相对Ioc 而言,依赖注入明确描述了被注入对象依赖IoC容器配置依赖对象!
Spring装配bean有三种方式
- 自动化配置(最推荐,避免显示配置带来的维护成本)
1.1 组件扫描:spring自动发现容器所创建的bean。开启组件扫描,默认情况下自动扫描配置类相同包(以及子包)下所有带有@component注解的类,并为其自动创建一个bean。
那么,如何开启组件扫描呢?
答:有两种方式,基于java配置(推荐)和xml配置
Java配置:在配置类上添加@componentScan注解
Xml配置:利用<context:component-scan base-package=”...”/>
1.2 自动装配:spring自动满足bean之间的依赖。使用@Autowired(推荐)或@Inject注解添加在构造、setter还是其他方法上,实现bean的自动注入,无需手动去new。
- Java配置(推荐,基于java语言,类型安全易于重构)
利用@Bean注解来声明(创建)一个bean,同时还可以注入另外依赖的bean(利用构造器和setter方法)
- Xml配置
3.1利用<bean>标签声明一个bean。
举例 <bean id = “peoson” class=”soundsystem.Person” />
3.2 如何注入bean(构造器和setter方法)
<bean id = “cdPlayer” class= “com.CDPlayer” >
<constructor-arg ref=”cd” />
</bean>
构造器注入字面量,举例
<bean id = “...” class= “...”>
<constructor-arg value=”...” />
</bean>
c命名空间注入bean,举例
<bean id = “...” class= “...”c:cd-ref=”...” />
c命名空间注入字面量,举例
<bean id = “...” class= “...” c:_...=”...” />
利用<list>、<set>等标签,构造器注入方式可以用来装配集合,而c命名空间不行。
setter方法注入bean,举例
<bean id = “cdPlayer” class= “com.CDPlayer” >
<property name = “...” ref = “...” />
</bean> 或者使用p命名空间
<bean id = “cdPlayer” class= “com.CDPlayer” p: cd-ref = “...” />
Setter方法注入字面量,举例
<bean id = “cdPlayer” class= “com.CDPlayer” >
<property name = “...” value = “...” />
</bean> 或者使用p命名空间
<bean id = “cdPlayer” class= “com.CDPlayer” p: ... = “...” />
AOP
Spring AOP是构成Spring框架的重要基石,spring AOP 构建在IOC 之上,和ioc浑然天成,统一于Sprig容器之中。
AOP有特定的应用场合:适合于具有特定逻辑的应用场合,如性能检测,访问控制,事物管理以及日志记录。
AOP(Aspect Oriented Programing)面向切面编程,根据软件重构的思想,如果多个类中出现相同的代码,将这些相同的代码提取到父类中,AOP通过横向抽取机制为这类无法通过纵向继承体系进行抽象的重复性代码提供了解决方案。
AOP术语:
1,连接点(JoinPooint):程序执行的某个特定位置,如类的开始初始化前,类的初始化后,类的某个方法调用前/后,方法抛出异常后。一个类或者一段代码拥有一些边界性质的特定点,这就是连接点。Spring仅支持方法的连接点,即仅能在方法调用前,方法调用后,方法抛出异常时及方法调用前后这些程序执行点织入增强;
2,切点(Pointcut):每个程序类都拥有多个连接点,在多个连接点中定位我们想要的连接点。AOP通过切点定位连接点,切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。。
3,增强(Advice):增强是织入到目标类连接点上的一段程序代码,Spring中增强除了用于描述一段程序代码外,还拥有另一个和连接点相关的信息,执行点的方位。Spring 提供的接口都是带方位名的:BeforeAdvice、AfterReturningAdvice,等
4,目标对象(Target):增强逻辑的织入目标类
5,引介(Introduction):引介是特殊的增强,他为类添加一些属性和方法。
6,织入(Weaving):将增强添加到目标类的具体链接点的过程。
7,代理(Proxy):
8,切面(Aspect):切面由切点和增强组成,他既包括横切逻辑的定义,也包括连接点的定义。
总的来说:AOP的工作重心在于如何将增强应用于目标对象的连接点上,这包括第一,如何通过切点和增强定位到连接点,第二,如何在增强中编写切面代码。
@Component("annotationTest")
@Aspect
public class AnnotationTest {
//定义切点
@Pointcut("execution(* *.saying(..))")
public void sayings(){}
@Before("sayings()")
public void sayHello(){
System.out.println("注解类型前置通知");
}
//后置通知
@After("sayings()")
public void sayGoodbey(){
System.out.println("注解类型后置通知");
}
//环绕通知。注意要有ProceedingJoinPoint参数传入。
@Around("sayings()")
public void sayAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("注解类型环绕通知..环绕前");
pjp.proceed();//执行方法
System.out.println("注解类型环绕通知..环绕后");
}
}
事物
spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。
spring事务特性
spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口
其中TransactionDefinition接口定义以下特性:
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
事务超时,所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务只读属性
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。默认为读写事务。
“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。
因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可
spring事务回滚规则
指spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。配置在抛出那些异常时回滚可以明确的事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
示例
spring中
1)引入相关事物相关依懒
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
2)引入相关约束
<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"
xmlns:context="http://www.springframework.org/schema/context" 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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
二、XML配置事务,和事物管理器
<?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"
xmlns:context="http://www.springframework.org/schema/context" 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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="lzgsea.jdbc"></context:component-scan>
<!-- 配置数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解@Transactional支持 -->
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
三、转账案例测事务
/***
编写AccountService模拟转账出现异常进行回滚
***/
@Service("accountService")
public class AccountService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void transfer() {
System.out.println("开始转账");
String sql = "update tb_account set money = money + ? where id = ?";
jdbcTemplate.update(sql, -1000,1);
//异常 测试是否回滚
int i = 1/0;
jdbcTemplate.update(sql, 1000,2);
System.out.println("操作完成");
}
}
3)测试
ApplicationContext act = new ClassPathXmlApplicationContext("transactional.xml");
AccountService accountService = (AccountService) act.getBean("accountService");
accountService.transfer();
spring boot
springboot开启事务很简单,只需要加上@Transactional 就可以了。因为在springboot中已经默认对jpa、jdbc、mybatis开启了事事务,引入它们依赖的时候,事物就默认开启。当然,如果你需要用其他的orm,比如beatlsql,需要配置事物管理器
有关this调用事物失效
@Service
public class TaskService {
@Autowired
private TaskManageDAO taskManageDAO;
@Transactional
public void test1(){
try {
this.test2(); //这里调用会使事务失效
/*
原因是:JDK的动态代理。
在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象
只有被动态代理直接调用的才会产生事务。
这里的this是(TaskService)真实对象而不是代理对象
*/
//解决方法
TaskService proxy =(TaskService) AopContext.currentProxy();
proxy.test2();
}catch (Exception e){
e.printStackTrace();
}
Task task = new Task();
task.setCompleteBy("wjl练习1");
task.setCompleteTime(new Date());
taskManageDAO.save(task);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
// 这个事务的意思是如果前面方法有事务存在,会将前面事务挂起,再重启一个新事务
public void test2(){
Task task = new Task();
task.setCompleteBy("wjl练习2");
task.setCompleteTime(new Date());
taskManageDAO.save(task);
throw new RuntimeException();
}