1. 概述
-
Spring是轻量级的开源的Java EE框架,可以解决企业应用开发的复杂性
-
两个核心:IOC和AOP
IOC:控制反转,把创建对象和对象调用的过程交给Spring管理
AOP:面向切面,不修改源代码的情况下进行功能增强
-
它有以下特点
1、方便解耦,简便开发
2、支持AOP
3、方便程序的测试
4、方便集成各种其他框架
5、方便进行实务操作
6、降低Java EE API使用难度
2. IOC
2.1 原理
1、在xml中配置相应的bean
2、解析xml得到bean的类型
3、通过反射创建对象
Class clazz = Class.forName("...");
return clazz.newInstance();
4、IOC思想是基于其容器完成的,容器底层本质上就是对象工厂。Spring提供了IOC容器的两种实现方式:
-
BeanFactory:是Spring内部的使用接口,一般不用于开发
加载配置文件时不会创建对象,在获取对象时才创建对象
-
ApplicationContext:是前者的子接口,功能更多,一般使用该接口开发
1、加载配置文件时就会创建对象
2、两个常用实现类FileSystemXmlApplicationContext和ClassPathXmlApplicationContext
2.2 Bean管理操作
Bean管理就是由Spring创建对象并且由Spring为对象注入属性。
2.2.1 Bean的作用域
Bean的作用域有两种:单例和多例,默认是单例。
(1)在Spring配置文件中,通过<bean>标签的scope属于可以设置是单例(singleton)还是多例(prototype)
(2)当是多例时,不是在加载配置文件时创建对象,而是调用getBean()获取时才创建
2.2.2 Bean的生命周期
(1)通过构造器创建bean实例
(2)为bean的属性赋值
(3)调用postProcessBeforeInitialization()方法(如果配置了后置处理器:自定义BeanPostProcessor实现类并重写其方法,在配置文件里声明这个自定义类即可)
(4)调用bean的初始化方法,在配置文件中使用init-method属性配置
(5)调用postProcessAfterInitialization()方法(如果配置了后置处理器)
(6)bean被使用
(7)当容器关闭时,调用bean的销毁方法,在配置文件中使用destroy-method属性配置
2.2.2 基于xml文件
1、基于xml文件创建对象
(1)在Spring配置文件中使用<bean>标签
(2)<bean>标签中有很多属性
(3)创建对象时,默认执行无参构造方法
2、基于xml文件注入属性
(1)DI:依赖注入
(2)使用set方法注入
<bean id="user1" class="pers.ljc.springtest.bean.User">
<property name="age" value="10"/>
<property name="name" value="张三"/>
</bean>
(3)使用有参构造方法注入
<bean id="user1" class="pers.ljc.springtest.bean.User">
<constructor-arg name="age" value="10"/>
<constructor-arg name="name" value="张三"/>
</bean>
2.2.2 基于注解
(1)创建对象,常用注解如下所示
- @Component(value = "..."),不写value的时候,默认是首字母小写的类名
- @Controller
- @Service
- @Repository
(2)属性注入
-
@Autowired:根据属性类型自动注入
-
@Qualifier:根据名称注入,要和@Autowired一起用
-
@Resource:即可根据类型注入,也可以根据名称注入。注意该注解是javax包里的
@Resource:什么都不写,根据类型注入
@Resource(name = "..."):根据名称注入
-
@Value:注入普通类型属性
3、完全注解开发
(1)创建配置类
@Configuration
// 组件扫描
@ComponentScan(basePackages = {"pers.ljc.springtest"})
// 开启代理
@EnableAspectJAutoProxy
public class MyConfig {
}
(2)使用AnnotationConfigApplicationContext容器来获取bean
@Test
public void test4() {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
UserDao userDao = context.getBean("userDaoImpl", UserDao.class);
userDao.add();
}
3. AOP
3.1 概念
面向切面编程:利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑的各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。在不修改源代码的情况下进行功能增强。
-
连接点:在一个类中,哪些方法可以被增强,这些方法就是连接点
-
切入点:实际被增强的方法
execution([权限修饰符] [返回类型] [全路径类] [方法名称] [参数列表]) 例子: // 增强User的say()方法,返回类型可省略 execution(* pers.ljc.springtest.bean.User.say(..)) // 增强User的所有方法 execution(* pers.ljc.springtest.bean.User.*(..)) // 包里面的所有类的所有方法 execution(* pers.ljc.springtest.bean.\*.\*(..)) -
通知(增强):实际增强的逻辑部分,如为某个方法增加记录日志,这里的记录日志就是通知。有五种
1、@Before:切入点之前执行
2、@AfterReturning:切入点之后执行,抛异常则不执行
3、@Around:切入点前后都执行
4、@AfterThrowing:切入点抛出异常后执行
5、@After:无论有没有抛异常,都执行
-
切面:指的是一个动作,把通知应用到切入点的过程
3.2 原理
AOP是通过动态代理实现的,动态代理有两种
(1)有接口的情况,使用JDK动态代理
(2)没有接口的情况,使用CGLIB动态代理
3.3 案例
Spring一般都是基于AspectJ(这是单独的框架,不是Spring的组成部分)实现AOP操作。
1、创建需要被增强的类
@Component
public class UserDao {
public void add() {
System.out.println("保存用户...");
}
}
2、创建增强类
@Component
@Aspect
public class UserDaoProxy {
// 前置通知
@Before(value = "execution(* pers.ljc.springtest.dao.UserDao.add(..))")
public void before() {
System.out.println("before");
}
}
3、进行通知的配置
(1)在Spring的配置文件中,开启注解扫描
(2)使用注解创建UserDao和UserDaoProxy
(3)在增强类上添加@Aspect注解
(4)在Spring配置文件中开启生成代理对象
<!--开启注解扫描-->
<context:component-scan base-package="pers.ljc.springtest" />
<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy />
4、配置不同类型的通知
5、测试方法
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = context.getBean("userDao", UserDao.class);
userDao.add();
}
五种类型的顺序如下图所示:
6、可以先定义一个切入点,然后再引用
@Pointcut(value = "execution(* pers.ljc.springtest.dao.UserDao.add(..))")
public void point() {}
@Before(value = "point()")
public void before() {
System.out.println("before");
}
7、当有多个增强类对同一个方法进行增强时,可在增强类上使用@Order(数值),数值越小优先级越高
4. 事务
事务是数据库操作的最基本单元,是一组操作,要么都成功,要么都成功。如果某个操作失败,则全部失败。
事务的特性(ACID):
- 原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态
- 一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定
- 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
- 持久性(Durability):当事务正确完成后,它对于数据的改变是永久性的
4.1 Spring的事务API
Spring提供了一个PlatformTransactionManager接口,代表事务管理器,这个接口针对不同的框架提供了不同的实现类。
(1)在配置文件中配置事务管理器
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<property name="initialPoolSize" value="${initPoolSize}"/>
<property name="maxPoolSize" value="${maxPoolSize}"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
(2)在配置文件中开启事务注解
<tx:annotation-driven transaction-manager="transactionManager"/>
(3)在service层的类或方法上加上@Transactional注解
(4)@Transactional注解的属性
- propagation:传播行为
- isolation:隔离级别
- timeout:超时时间
- readOnly:是否只读
- rollbackFor:回滚
- noRollbackFor:不回滚
4.2 事务的传播行为
methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
4.2.1 PROPAGATION_REQUIRED
表示当前方法必须运行在事务中。如果当前事务存在,方法就会在该事务中运行。否则,会启动一个新事务。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// do something
}
- 单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
- 调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。
4.2.2 PROPAGATION_SUPPORTS
表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// do something
}
- 单独调用methodB时,methodB方法是非事务地执行的。
- 当调用methdA时,methodB则加入了methodA的事务中,事务地执行。
4.2.3 PROPAGATION_MANDATORY
表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事务属性为MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// do something
}
- 单独调用methodB时,因为当前没有一个活动的事务,则会抛出IllegalTransactionStateException异常。
- 当调用methodA时,methodB则加入到methodA的事务中,事务地执行。
4.2.4 PROPAGATION_REQUIRES_NEW
表示当前方法必须运行在它自己的事务中。会新建事务,如果当前存在事务,则把当前事务挂起。使用JTATransactionManager作为事务管理器。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
// do something else
}
// 事务属性为REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// do something
}
则调用methodA方法相当于调用以下方法
public void methodA() {
TransactionManager tm = null;
try{
tm = getTransactionManager(); // 获得一个JTA事务管理器
tm.begin(); // 开启一个新的事务
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend(); // 挂起当前事务
try{
tm.begin(); // 重新开启第二个事务
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit(); // 提交第二个事务
} Catch(RunTimeException ex) {
ts2.rollback(); // 回滚第二个事务
} finally {
// 释放资源
}
tm.resume(ts1); // methodB执行完后,恢复第一个事务
doSomeThingB();
ts1.commit(); // 提交第一个事务
} catch(RunTimeException ex) {
ts1.rollback(); // 回滚第一个事务
} finally {
// 释放资源
}
}
在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果仍然被提交。而除了methodB之外的其它代码导致的结果都会被回滚。
4.2.5 PROPAGATION_NOT_SUPPORTED
表示该方法不应该运行在事务中。如果存在当前事务,则把该事务挂起。
4.2.6 PROPAGATION_NEVER
表示该方法不应该运行在事务中。如果存在当前事务,则会抛出异常。
4.2.7 PROPAGATION_NESTED
- 如果一个活动的事务存在,则运行在一个嵌套的事务中。
- 如果没有活动事务, 则按
TransactionDefinition.PROPAGATION_REQUIRED属性执行。 - 这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。
- 需要JDBC 驱动的java.sql.Savepoint类。
- 需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
}
@Transactional(propagation = Propagation.NEWSTED)
public void methodB() {
……
}
如果单独调用methodB()方法,则按TransactionDefinition.PROPAGATION_REQUIRED属性执行。如果调用methodA()方法,相当于以下效果
public void methodA() {
Connection con = null;
Savepoint savepoint = null;
try{
con = getConnection();
con.setAutoCommit(false);
doSomeThingA();
savepoint = con2.setSavepoint();
try {
methodB();
} catch(RuntimeException ex) {
con.rollback(savepoint);
} finally {
// 释放资源
}
doSomeThingB();
con.commit();
} catch(RuntimeException ex) {
con.rollback();
} finally {
// 释放资源
}
}
当methodB()方法调用之前,调用setSavepoint()方法,保存当前的状态到。如果methodB()方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码如doSomeThingB()方法调用失败,则回滚包括methodB()方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作;而内层事务操作失败并不会引起外层事务的回滚。
4.2.8 NESTED与REQUIRES_NEW区别
- 使用
PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。 PROPAGATION_REQUIRES_NEW启动一个新的,不依赖于环境的 “内部” 事务。这个事务将被提交或回滚而不依赖于外部事务,它拥有自己的隔离范围,自己的锁,等等。当内部事务开始执行时,外部事务将被挂起,内务事务结束时,外部事务将继续执行。PROPAGATION_REQUIRES_NEW和PROPAGATION_NESTED的最大区别在于,前者完全是一个新的事务,而后者则是外部事务的子事务,如果外部事务提交,嵌套事务也会被提交,这个规则同样适用于回滚。