文章目录
Spring学习笔记
第一部分:Spring 概述
1.1 Spring 简介
- 轻量级开源框架
- 核心是IOC和AOP
- 使⽤最多的 Java EE 企业应⽤开源框架
1.2 Spring 发展历程
EJB->Expert One-to-One J2EE Design and Development论文->Spring
1.3 Spring 的优势
- ⽅便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的
过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更
专注于上层的应⽤。
- AOP编程的⽀持
通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过
AOP轻松应付。
- 声明式事务的⽀持
@Transactional
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼
开发效率和质量。
- ⽅便程序的测试
- ⽅便集成各种优秀框架
- 降低JavaEE API的使⽤难度
- 源码是经典的 Java 学习范例
1.4 Spring 的核⼼结构
主要包括⼏个⼤模块:
Spring核⼼容器(Core Container)
容器是Spring框架最核⼼的部分,它管理着Spring应⽤中
bean的创建、配置和管理。在该模块中,包括了Spring bean⼯⼚,它为Spring提供了DI的功能。
基于bean⼯⼚,我们还会发现有多种Spring应⽤上下⽂的实现。所有的Spring模块都构建于核⼼
容器之上。
⾯向切⾯编程(AOP) /Aspects
Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应
⽤系统中开发切⾯的基础,与DI⼀样, AOP可以帮助应⽤对象解耦。
数据访问与集成(Data Access/Integration)
Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专
注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外, Spring AOP为数据访问
提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、 MyBatis等。该模块由
JDBC、 Transactions、 ORM、 OXM 和 JMS 等模块组成。
Web
该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅
案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平。
Test
为了使得开发者能够很⽅便的进⾏测试, Spring提供了测试模块以致⼒于Spring应⽤的测
试。 通过该模块, Spring为使⽤Servlet、 JNDI等编写单元测试提供了⼀系列的mock对象实现。
第二部分:核心思想
IOC和AOP不是spring提出的,在spring之前就已经存在,只不过更偏向于理论化, spring在技
术层次把这两个思想做了⾮常好的实现(Java)
1.1 IoC
IOC:即控制反转,它是⼀个技术思想,不是⼀个技术实现
控制:指的是对象创建(实例化、管理)的权利
反转:控制权交给外部环境了(spring框架、 IoC容器)
1.1.1 IoC解决了什么问题
IoC解决对象之间的耦合问题
1.1.2 IoC和DI的区别
IOC和DI描述的是同⼀件事情,只不过⻆度不⼀样罢了
IOC是站在对象的角度,对象实例化及其管理的权利靠给了容器;
DI是站在容器的角度,容器会把对象依赖的其他对象注入进来
1.2 AOP
1.2.1 什么是AOP
AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程
1.2.2 AOP在解决什么问题
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
1.2.3 为什么叫做⾯向切⾯编程
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑
「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个
⾯的概念在⾥⾯
第三部分:⼿写实现 IoC 和 AOP
1.1 问题分析
1)问题⼀:service 层实现类在使⽤ dao 层对象时,直接在
service层new出dao层对
象,然⽽⼀个 new 关键字却将service 和 dao 层具体的⼀个实现类
耦合在了⼀起,如果说技术架构发⽣⼀些变动, dao 层的实现要使⽤其它技术,
⽐如 Mybatis,思考切换起来的成本?每⼀个 new 的地⽅都需要修改源代码,重新编译,⾯向接⼝开发
的意义将⼤打折扣?
(2)问题⼆: service 层代码没有竟然还没有进⾏事务控制 ?!如果转账过程中出现异常,将可能导致
数据库数据错乱,后果可能会很严重,尤其在⾦融业务。
1.2 解决思路
针对问题⼀:
实例化对象的⽅式除了 new 之外,还有什么技术?反射 (需要把类的全限定类名配置在xml
中)
考虑使⽤设计模式中的⼯⼚模式解耦合,另外项⽬中往往有很多对象需要实例化,那就在⼯⼚中使
⽤反 射技术实例化对象,⼯⼚模式很合适
针对问题⼆:
service 层没有添加事务控制,怎么办?没有事务就添加上事务控制,⼿动控制 JDBC 的
Connection 事务,但要注意将Connection和当前线程绑定(即保证⼀个线程只有⼀个
Connection,这样操作才针对的是同⼀个 Connection,进⽽控制的是同⼀个事务,考虑ThreadLocal
)
1.3 手写改造实现
针对问题一的改造:通过对象工厂来实例化需要的bean
public class BeanFactory {
/**
* ⼯⼚类的两个任务
* 任务⼀:加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放⼊
map待⽤
* 任务⼆:提供接⼝⽅法根据id从map中获取bean(静态⽅法)
*/
private static Map<String,Object> map = new HashMap<>();
static {
InputStream resourceAsStream =
BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("//bean");
// 实例化bean对象
for (int i = 0; i < list.size(); i++) {
Element element = list.get(i);
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance();
map.put(id,o);
}
// 维护bean之间的依赖关系
List<Element> propertyNodes =
rootElement.selectNodes("//property");
for (int i = 0; i < propertyNodes.size(); i++) {
Element element = propertyNodes.get(i);
// 处理property元素
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
String parentId =
element.getParent().attributeValue("id");
Object parentObject = map.get(parentId);
Method[] methods = parentObject.getClass().getMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if(("set" + name).equalsIgnoreCase(method.getName()))
{
// bean之间的依赖关系(注⼊bean)
Object propertyObject = map.get(ref);
method.invoke(parentObject,propertyObject);
}
}
// 维护依赖关系后重新将bean放⼊map中
map.put(parentId,parentObject);
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public static Object getBean(String id) {
return map.get(id);
}
}
针对问题二的改造:增加事务管理类,封装事务操作,通过AOP动态代理自动完成事务操作
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
// 开启事务
public void beginTransaction() throws SQLException {
connectionUtils.getCurrentThreadConn().setAutoCommit(false);
}
// 提交事务
public void commit() throws SQLException {
connectionUtils.getCurrentThreadConn().commit();
}
// 回滚事务
public void rollback() throws SQLException {
connectionUtils.getCurrentThreadConn().rollback();
}
}
public class ConnectionUtils {
/*private ConnectionUtils() {
}
private static ConnectionUtils connectionUtils = new
ConnectionUtils();
public static ConnectionUtils getInstance() {
return connectionUtils;
}*/
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); //存储当前线程的连接
/**
* 从当前线程获取连接
*/
public Connection getCurrentThreadConn() throws SQLException {
/**
* 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取⼀个连接绑定到
当前线程
*/
Connection connection = threadLocal.get();
if(connection == null) {
// 从连接池拿连接并绑定到线程
connection = DruidUtils.getInstance().getConnection();
// 绑定到当前线程
threadLocal.set(connection);
}
return connection;
}
}
/**
*代理工厂
**/
public class ProxyFactory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager
transactionManager) {
this.transactionManager = transactionManager;
}
public Object getProxy(Object target) {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[]
args) throws Throwable {
Object result = null;
try{
// 开启事务
transactionManager.beginTransaction();
// 调⽤原有业务逻辑
result = method.invoke(target,args);
// 提交事务
transactionManager.commit();
}catch(Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 异常向上抛出,便于servlet中捕获
throw e.getCause();
}
return result;
}
});
}
}
第四部分:Spring IOC 应⽤
1.1 IOC基础应用
BeanFactory:BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范
ApplicationContext:ApplicationContext是BeanFactory的⼀个⼦接⼝,是容器的⾼级接⼝,⽐
BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml, java配置类)等等
1.1.1 xml模式下实例化bean的三种方式
-
⽅式⼀:使⽤⽆参构造函数
在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建
失败。<!--配置service对象--> <bean id="userService" class="com.lagou.service.impl.TransferServiceImpl"> </bean>
-
⽅式⼆:使⽤静态⽅法创建
<!--使⽤静态⽅法创建对象的配置⽅式--> <bean id="userService" class="com.lagou.factory.BeanFactory" factory-method="getTransferService"></bean>
-
⽅式三:使⽤实例化⽅法创建
<!--使⽤实例⽅法创建对象的配置⽅式-->
<bean id="beanFactory"
class="com.lagou.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService"></bean>
1.1.2 bean的作用域
单例模式: singleton
对象出⽣:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
多例模式: prototype
对象出⽣:当使⽤对象时,创建新的对象实例。
对象活着:只要对象在使⽤中,就⼀直活着。
对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
⼀句话总结:多例模式的bean对象, spring框架只负责创建,不负责销毁。
1.2 Spring IOC⾼级特性
1.2.1 lazy-Init 延迟加载
设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器
通过 getBean 索取 bean 时实例化的。
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />
如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不
会实例化bean,⽽是调⽤ getBean ⽅法实例化的。
应⽤场景
(1)开启延迟加载⼀定程度提⾼容器启动和运转性能
<bean id="testBean" class="cn.lagou.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />
<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>
(2)对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占
⽤资源
1.2.2 FactoryBean
<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
<property name="companyInfo" value="拉勾,中关村,500"/>
</bean>
public class CompanyFactoryBean implements FactoryBean<Company> {
private String companyInfo; // 公司名称,地址,规模
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
@Override
public Company getObject() throws Exception {
// 模拟创建复杂对象Company
Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));
return company;
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
测试
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='拉勾', address='中关村', scale=500}
如果需要获取FactoryBean本身,需要在id之前添加“&”,如Object companyBean = applicationContext.getBean("&companyBean");
1.2.3 后置处理器
BeanPostProcessor
Spring容器的实例化和依赖注⼊之后,该接⼝提供了两个⽅法,分别在Bean的初始化⽅法前和初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法
BeanFactoryPostProcessor
BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应
⽤:PropertyPlaceholderConfigurer
第五部分:Spring IOC源码深度剖析
1、源码阅读原则:
定焦原则:抓主线
宏观原则:站在上帝视⻆,关注源码结构和业务流程(淡化具体某⾏代码的编写细节)
2、源码阅读技巧:
-
断点(观察调⽤栈)
-
反调(Find Usages)
-
经验(spring框架中doXXX,做具体处理的地⽅)
1.1 Spring IoC容器初始化主体流程
1.2 Spring IoC容器初始化主流程
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 第⼀步:刷新前的预处理
prepareRefresh();
/*
第⼆步:
获取BeanFactory;默认实现是DefaultListableBeanFactory
加载BeanDefition 并注册到 BeanDefitionRegistry
*/ConfigurableListableBeanFactory beanFactory =
obtainFreshBeanFactory();
// 第三步: BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加
载器等)
prepareBeanFactory(beanFactory);
try {
// 第四步: BeanFactory准备⼯作完成后进⾏的后置处理⼯作
postProcessBeanFactory(beanFactory);
// 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执
⾏
registerBeanPostProcessors(beanFactory);
// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
initMessageSource();
// 第⼋步:初始化事件派发器
initApplicationEventMulticaster();
// 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
onRefresh();
// 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器
bean
registerListeners();
/*
第⼗⼀步:
初始化所有剩下的⾮懒加载的单例bean
初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
填充属性
初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、 init-method⽅法)
调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
*/
finishBeanFactoryInitialization(beanFactory);
/*
第⼗⼆步:
完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
件 (ContextRefreshedEvent)
*/
finishRefresh();
}
......
}
}
1.3 BeanFactory创建流程
1.4 Bean创建流程
Bean创建⼦流程⼊⼝在
AbstractApplicationContext#refresh()⽅法的finishBeanFactoryInitialization(beanFactory)
1.5 lazy-init 延迟加载机制原理
普通 Bean 的初始化是在容器启动初始化阶段执⾏的,⽽被lazy-init=true修饰的 bean 则是在从容器⾥
第⼀次进⾏context.getBean() 时进⾏触发。 Spring 启动的时候会把所有bean信息(包括XML和注解)解
析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个
BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进
⾏初始化并依赖注⼊。
public void preInstantiateSingletons() throws BeansException {
// 所有beanDefinition集合
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// 触发所有⾮懒加载单例bean的初始化
for (String beanName : beanNames) {
// 获取bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是 FactoryBean
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>)
getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof
SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new
PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
}else {
/*
如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑
和
懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的
*/
getBean(beanName);
}
}
}
1.6 Spring IoC循环依赖问题
注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结
条件。
Spring中循环依赖场景有:
-
构造器的循环依赖(构造器注⼊)
-
Field 属性的循环依赖(set注⼊)
其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决
属性循环依赖时, spring采⽤的是提前暴露对象的⽅法。
单例 bean 构造器参数循环依赖(⽆法解决)
prototype 原型 bean循环依赖(⽆法解决)
Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对
象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法
之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//将初始化后的对象提前已ObjectFactory对象注⼊到容器中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
- ClassA调⽤setClassB⽅法, Spring⾸先尝试从容器中获取ClassB,此时ClassB不存在Spring
容器中。 - Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中
- ClassB调⽤setClassA⽅法, Spring从容器中获取ClassA ,因为第⼀步中已经提前暴露了
ClassA,因此可以获取到ClassA实例- ClassA通过spring容器获取到ClassB,完成了对象初始化操作。
这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。
第六部分:Spring AOP 应⽤
1.1 相关术语
1.2 代理方式
Spring 实现AOP思想使⽤的是动态代理技术
默认情况下, Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现
任何接⼝时, Spring会选择CGLIB。当被代理对象实现了接⼝, Spring会选择JDK官⽅的代理技术,不过
我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。
1.3 Spring中AOP的配置⽅式
xml模式
<!--
Spring基于XML的AOP配置前期准备:
在spring的配置⽂件中加⼊aop的约束
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
Spring基于XML的AOP配置步骤:
第⼀步:把通知Bean交给Spring管理
第⼆步:使⽤aop:config开始aop的配置
第三步:使⽤aop:aspect配置切⾯
第四步:使⽤对应的标签配置通知的类型
⼊⻔案例采⽤前置通知,标签为aop:before
-->
<!--把通知bean交给spring来管理-->
<bean id="logUtil" class="com.lagou.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
<!--配置切⾯-->
<aop:aspect id="logAdvice" ref="logUtil">
<!--配置前置通知-->
<aop:before method="printLog" pointcut="execution(public *
com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou
.pojo.Account))"></aop:before>
</aop:aspect>
</aop:config>>
五种通知类型:
前置通知
配置⽅式: aop:before标签
包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
*
…TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Accou
nt)
包名可以使⽤…表示当前包及其⼦包
*
…TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account
)
类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
-
…(com.lagou.pojo.Account)
参数列表,可以使⽤具体类型
基本类型直接写类型名称 : int
引⽤类型必须写全限定类名: java.lang.String
参数列表可以使⽤*,表示任意参数类型,但是必须有参数 -
….()
参数列表可以使⽤…,表示有⽆参数均可。有参数可以是任意类型 -
….*(…)
全通配⽅式: -
….*(…)
<aop:config proxy-target-class=“true”><aop:aspectj-autoproxy proxy-target-class=“true”></aop:aspectjautoproxy>执⾏时机
前置通知永远都会在切⼊点⽅法(业务核⼼⽅法)执⾏之前执⾏。
细节
前置通知可以获取切⼊点⽅法的参数,并对其进⾏增强。
正常执⾏时通知
配置⽅式
异常通知
配置⽅式<aop:before method=“printLog” pointcut-ref=“pointcut1”>
</aop:before><aop:after-returning method=“afterReturningPrintLog” pointcutref=“pt1”></aop:after-returning>执⾏时机
异常通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏产⽣异常之后,异常通知执⾏。如果切
⼊点⽅法执⾏没有产⽣异常,则异常通知不会执⾏。
细节
异常通知不仅可以获取切⼊点⽅法执⾏的参数,也可以获取切⼊点⽅法执⾏产⽣的异常信息。
最终通知
配置⽅式
执⾏时机
最终通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏完成之后,切⼊点⽅法返回之前执⾏。
换句话说,⽆论切⼊点⽅法执⾏是否产⽣异常,它都会在返回之前执⾏。
细节
最终通知执⾏时,可以获取到通知⽅法的参数。同时它可以做⼀些清理操作。
环绕通知
配置⽅式<aop:after-throwing method=“afterThrowingPrintLog” pointcut-ref=“pt1”>
</aop:after-throwing>
<aop:after method=“afterPrintLog” pointcut-ref=“pt1”></aop:after>4.2 XML+注解模式
XML 中开启 Spring 对注解 AOP 的⽀持
示例<aop:around method=“aroundPrintLog” pointcut-ref=“pt1”></aop:around>
xml+注解
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
@Pointcut
@Before
@AfterReturning
@AfterThrowing
@After
@Around
注解
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
public class SpringConfiguration {
}
1.7 Spring 声明式事务的⽀持
编程式事务: 在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
声明式事务: 通过xml或者注解配置的⽅式达到事务控制的⽬的,叫做声明式事务
核心API:PlatformTransactionManager、TransactionStatus、TransactionDefinetion
xml配置方式
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--定制事务细节,传播⾏为、隔离级别等-->
<tx:attributes>
<!--⼀般性配置-->
<tx:method name="*" read-only="false"
propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
<!--针对查询的覆盖性配置-->
<tx:method name="query*" read-only="true"
propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--advice-ref指向增强=横切逻辑+⽅位-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(*
com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
</aop:config>
xml+注解方式
<!--配置事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManage
r">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的⽀持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
纯注解方式
@EnableTransactionManagement//开启spring注解事务的⽀持
public class SpringConfiguration {
}
第七部分:Spring AOP源码深度剖析
1.1 代理对象创建
AOP源码分析类⽅法调⽤关系:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#i
nitializeBean
调⽤
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#a
pplyBeanPostProcessorsAfterInitialization
调用
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProce
ssAfterInitialization(后置处理器AbstractAutoProxyCreator完成bean代理对象创建)
调⽤
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNec
essary
调⽤
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy (在这⼀步把委托对象的aop增强和通⽤拦截进⾏合并,最终给代理对象)
调⽤
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
调⽤
org.springframework.aop.framework.CglibAopProxy#getProxy(java.lang.ClassLoader
)
1.2 Spring声明式事务控制
AbstractAutoProxyCreator是一个后置处理器
声明式事务分析:
@EnableTransactionManagement 注解
1)通过@import引⼊了TransactionManagementConfigurationSelector类
它的selectImports⽅法导⼊了另外两个类: AutoProxyRegistrar和
ProxyTransactionManagementConfiguration
2) AutoProxyRegistrar类分析
⽅法registerBeanDefinitions中,引⼊了其他类,通过
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)引⼊
InfrastructureAdvisorAutoProxyCreator,
它继承了AbstractAutoProxyCreator,是⼀个
后置处理器类
3) ProxyTransactionManagementConfiguration 是⼀个添加了@Configuration注解的配置类
(注册bean)
注册事务增强器(注⼊属性解析器、事务拦截器)
属性解析器: AnnotationTransactionAttributeSource,内部持有了⼀个解析器集合
Set annotationParsers;
具体使⽤的是SpringTransactionAnnotationParser解析器,⽤来解析
@Transactional的事务属性
事务拦截器: TransactionInterceptor实现了MethodInterceptor接⼝,该通⽤拦截
会在产⽣代理对象之前和aop增强合并,最终⼀起影响到代理对象
TransactionInterceptor的invoke⽅法中invokeWithinTransaction会触发原有业
务逻辑调⽤(增强事务)