本文对Spring相关知识点做了归纳整理,包括 Spring 优势、其框架结构、核心思想,并对IoC思想及AOP思想进行手动实现,增强对Spring 核心思想的理解。之后对Spring IoC、AOP 的实现方式和特性进行介绍,并对照源码理解其实现思路。
方便解耦,简化开发
[注:IoC(降低组件耦合性)、DI(降低业务对象替换的复杂性)]
AOP编程的思想
[注:通用任务集中管理,使得代码可更好的复用]
声明式事务的支持
[注:@Transaction]
轻量级框架,代码侵入性低,可自由选择框架部分组件
方便集成各种优秀框架
IoC 是一个技术思想,而Spring对其进行了实现
控制反转:对象创建(实例化、管理)的权利交给外部环境(Spring框架、IoC容器)
DI(Dependancy Injection)依赖注⼊
IoC和DI描述的是同一件事情(对象实例化及依赖关系维护),只不过角度不一样罢了 IoC:站在对象的角度,对象实例化及其管理交给容器 DI:站在容器角度,容器会把对象依赖的其他对象注入
OOP是一种垂直继承体系(三大特征:封装、继承和多态)
纯 xml 实现(bean 信息定义全部配置在 xml 中)
// JavaSE 应用启动 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); // 或者读取绝对路径下的配置文件(不推荐) ApplicationContext applicationContext = new FileSystemXmlApplicationContext("c:/beans.xml"); // JavaWeb 应用 /** 配置 ContextLoaderListener 类(web.xml) 监听器机制加载 xml */ 复制代码
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <!--配置Spring ioc容器的配置⽂件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!--使⽤监听器启动Spring的IOC容器--> <listener> <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass> </listener> </web-app> 复制代码
xml + 注解(部分 bean 使用 xml 定义,部分 bean 使用注解定义)
和纯 xml 实现一致
纯注解 (所有 bean 都是用注解来定义)
// JavaSE 应用启动 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring.class); // JavaWeb 应用 /** 配置 ContextLoaderListener 类(web.xml) 监听器机制加载 注解配置类 */ 复制代码
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器--> <context-param> <param-name>contextClass</param-name> <paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli cationContext</param-value> </context-param> <!--配置启动类的全限定类名--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.lagou.edu.SpringConfig</param-value> </context-param> <!--使⽤监听器启动Spring的IOC容器--> <listener> <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass> </listener> </web-app> 复制代码
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。 ApplicationContext是容器的⾼级接⼝,⽐ BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等
使用无参构造器
<!--配置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" factory-method="getTransferService"></bean> 复制代码
singleton(单例模式)
单例模式的 bean 对象生命周期与容器相同
prototype(多例模式)
多例模式的 bean 对象,Spring 框架只负责创建,不负责销毁
依赖注入分类
按注入的方式分类
按注入的数据类型分类
示例( 以set方法注入为例,构造函数注入同样 ):
<!-- Array --> <property name="myArray"> <array> <value>array1</value> <value>array2</value> </array> </property> <!-- List --> <property name="myArray"> <list> <value>list1</value> <value>list2</value> </list> </property> <!-- Set --> <property name="mySet"> <set> <value>set1</value> <value>set2</value> </set> </property> <!-- Map --> <property name="myMap"> <map> <entry key="key1" value="value1"/> <entry key="key2" value="value2"/> </map> </property> <!-- Properties --> <property name="myProperties"> <map> <prop key="key1" value="value1"/> <prop key="key2" value="value2"/> </map> </property> 复制代码
在List结构的集合数据注⼊时,array , list , set这三个标签通⽤,另外注值的value标签内部 可以直接写值,也可以使⽤bean标签配置⼀个对象,或者⽤ref标签引⽤⼀个已经配合的bean 的唯⼀标识。 在Map结构的集合数据注⼊时,map标签使⽤entry⼦标签实现数据注⼊,entry标签可以使 ⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。 同时entry标签中也可以使⽤ref标签,但是不能使⽤bean标签。⽽property标签中不能使 ⽤ref或者bean标签引⽤对象
xml 与注解相结合模式下,通常第三方jar 中的bean 定义在 xml 中,自己开发的 bean 定义使用注解
配置方面
IoC
xml形式 | 对应的注解形式 |
---|---|
<bean> 标签 | @Component("accountDao"),注解加在类上 bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类 的类名⾸字⺟⼩写; 另外,针对分层代码开发提供了@Componenet的三种别名@Controller、 @Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这 四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已 |
scope属性 | @Scope("prototype"),默认单例,注解加在类上 |
标签的 init-method 属性 | @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 |
destory-method 属性 | @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |
DI
作用:
BeanFactory接口:容器顶级接口,定义容器的一些基础行为
FactoryBean接口:借助他自定义Bean,作用类似于 Bean 创建方式的静态方法和实例化方法
使用方法
创建 Bean 对应的 Factory 类,实现 FactoryBean 接口,重写 getObject() 、getObjectType() 和 isSingleton()
在 xml 中配置该 Factory 类
容器初始化过程中 会自动实例化 Factory 对象并调用其 getObject() 并管理
注:如果需要 Factory 对象,可获取容器中的 &${id} 对象
<bean id="testBean" class="com.test.TestBeanFactory"></bean> <!-- getBean("testBean") 获取的是 TestBean 对象 getBean("&testBean") 获取的是 TestBeanFactory 对象 --> 复制代码
Spring 提供两种后置处理 bean 的扩展接口
该接口提供两个方法:
BeanFactoryPostProcessor:BeanFactory 初始化后(bean 还未实例化,此时 bean 刚被解析成 BeanDefinition对象)调用
此接口提供了⼀个⽅法,⽅法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的 BeanDefinition对象。然后我们可以对定义的属性进⾏修改。
**BeanDefinition对象:**我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean, 这个JavaBean 就是 BeanDefinition
![](img/Spring IoC容器继承体系.jpg)
Bean对象创建的⼏个关键时机点代码层级的调⽤都在 AbstractApplicationContext 类 的 refresh ⽅法中
关键点 | 触发代码 |
---|---|
构造器 | refresh#finishBeanFactoryInitialization(beanFactory)(beanFactory) |
BeanFactoryPostProcessor 初始化 | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanFactoryPostProcessor ⽅法调⽤ | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanPostProcessor 初始化 | registerBeanPostProcessors(beanFactory) |
BeanPostProcessor ⽅法调⽤ | refresh#finishBeanFactoryInitialization(beanFactory) |
源码如下:
public void refresh() throws BeansException, IllegalStateException { // 对象锁加锁 synchronized (this.startupShutdownMonitor) { /* Prepare this context for refreshing. 刷新前的预处理 表示在真正做refresh操作之前需要准备做的事情: 设置Spring容器的启动时间, 开启活跃状态,撤销关闭状态 验证环境信息里一些必须存在的属性等 */ prepareRefresh(); /* Tell the subclass to refresh the internal bean factory. 获取BeanFactory;默认实现是DefaultListableBeanFactory 加载BeanDefition 并注册到 BeanDefitionRegistry */ ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); /* Prepare the bean factory for use in this context. BeanFactory的预准备工作(BeanFactory进行一些设置,比如context的类加载器等) */ prepareBeanFactory(beanFactory); try { /* Allows post-processing of the bean factory in context subclasses. BeanFactory准备工作完成后进行的后置处理工作 */ postProcessBeanFactory(beanFactory); /* Invoke factory processors registered as beans in the context. 实例化实现了BeanFactoryPostProcessor接口的Bean,并调用接口方法 */ invokeBeanFactoryPostProcessors(beanFactory); /* Register bean processors that intercept bean creation. 注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行 */ registerBeanPostProcessors(beanFactory); /* Initialize message source for this context. 初始化MessageSource组件(做国际化功能;消息绑定,消息解析); */ initMessageSource(); /* Initialize event multicaster for this context. 初始化事件派发器 */ initApplicationEventMulticaster(); /* Initialize other special beans in specific context subclasses. 子类重写这个方法,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器 */ onRefresh(); /* Check for listener beans and register them. 注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean */ registerListeners(); /* Instantiate all remaining (non-lazy-init) singletons. 初始化所有剩下的非懒加载的单例bean 初始化创建非懒加载方式的单例Bean实例(未设置属性) 填充属性 初始化方法调用(比如调用afterPropertiesSet方法、init-method方法) 调用BeanPostProcessor(后置处理器)对实例bean进行后置处理 */ finishBeanFactoryInitialization(beanFactory); /* Last step: publish corresponding event. 完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent) */ finishRefresh(); }catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex);} // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; }finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } 复制代码
注:参考IoC 循环依赖时序图
public void preInstantiateSingletons() throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this); } // Iterate over a copy to allow for init methods which in turn register new bean definitions. // While this may not be part of the regular factory bootstrap, it does otherwise work fine. // 所有bean的名字 List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // Trigger initialization of all non-lazy singleton beans... // 触发所有非延迟加载单例bean的初始化,主要步骤为getBean for (String beanName : beanNames) { // 合并父BeanDefinition对象 // map.get(beanName) RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); // 如果是FactoryBean则加& if (bean instanceof FactoryBean) { final FactoryBean<?> factory = (FactoryBean<?>) bean; boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController .doPrivileged((PrivilegedAction<Boolean>)((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext()); }else { isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit()); } if (isEagerInit) { getBean(beanName); } } }else { // 实例化当前bean getBean(beanName); } } } // Trigger post-initialization callback for all applicable beans... for (String beanName : beanNames) { Object singletonInstance = getSingleton(beanName); if (singletonInstance instanceof SmartInitializingSingleton) { final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { smartSingleton.afterSingletonsInstantiated(); return null; }, getAccessControlContext()); }else { smartSingleton.afterSingletonsInstantiated(); } } } } 复制代码
如源码:
单例 bean 构造器参数循环依赖(⽆法解决)
prototype 原型 bean循环依赖(⽆法解决)
对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依 赖,Spring都 会直接报错处理。
单例bean通过setXxx或者@Autowired进⾏循环依赖
Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前
Spring 实现AOP 思想使用的是动态代理技术
默认情况下
如果 被代理类 实现接口 ⇒ 选择 JDK 动态代理
否则 ⇒ 选择 CGLIB 动态代理
可通过配置方式设置强制使用 CGLIB 动态代理
需要注意的点:
切入点表达式
指的是遵循特定语法结构的字符串,其 作⽤是⽤于对符合语法格式的连接点进⾏增强。
// 示例 // 全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表) // 全匹配⽅式: public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) // 访问修饰符可以省略 void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) // 返回值可以使⽤*,表示任意返回值 * com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) // 包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个 * ....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) // 包名可以使⽤..表示当前包及其⼦包 * ..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) // 类名和⽅法名,都可以使⽤.表示任意类,任意⽅法 * ...(com.lagou.pojo.Account) // 参数列表,可以使⽤具体类型 基本类型直接写类型名称 : int // 引⽤类型必须写全限定类名:java.lang.String // 参数列表可以使⽤*,表示任意参数类型,但是必须有参数 * *..*.*(*) // 参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型 * *..*.*(..) // 全通配⽅式: * *..*.*(..) 复制代码
改变代理方式的配置
使用<aop:config> 标签配置
<aop:config proxy-target-class="true"> 复制代码
使⽤<aop:aspectj-autoproxy>标签配置
<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP 的⽀持--> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy> 复制代码
五种通知类型
前置通知:切入点方法执行前执行
<aop:before>
后置通知:切入点方法正常执行后执行
<aop:after-returning>
异常通知:切⼊点⽅法执⾏产⽣异常之后执⾏
<aop:after-throwing>
最终通知:切⼊点⽅法执⾏完成之后,切⼊点⽅法返回之前执⾏(无论是否异常都会执行,finally)
<aop:after>
环绕通知:较特殊,借助的ProceedingJoinPoint接⼝及其实现类, 实现⼿动触发切⼊点⽅法的调⽤
XML 中开启 Spring 对注解 AOP 的⽀持
<!--开启spring对注解aop的⽀持--> <aop:aspectj-autoproxy/> 复制代码
示例
/** * 模拟记录⽇志 */ @Component @Aspect public class LogUtil { /** * 我们在xml中已经使⽤了通⽤切⼊点表达式,供多个切⾯使⽤,那么在注解中如何使⽤呢?* 第⼀步:编写⼀个⽅法 * 第⼆步:在⽅法使⽤@Pointcut注解 * 第三步:给注解的value属性提供切⼊点表达式 * 细节: * 1.在引⽤切⼊点表达式时,必须是⽅法名+(),例如"pointcut()"。 * 2.在当前切⾯中使⽤,可以直接写⽅法名。在其他切⾯中使⽤必须是全限定⽅法名。 */ @Pointcut("execution(* com.lagou.service.impl.*.*(..))") public void pointcut(){} @Before("pointcut()") public void beforePrintLog(JoinPoint jp){ Object[] args = jp.getArgs(); System.out.println("前置通知:beforePrintLog,参数是:"+Arrays.toString(args)); } @AfterReturning(value = "pointcut()",returning = "rtValue") public void afterReturningPrintLog(Object rtValue){ System.out.println("后置通知:afterReturningPrintLog,返回值是:"+rtValue); } @AfterThrowing(value = "pointcut()",throwing = "e") public void afterThrowingPrintLog(Throwable e){ System.out.println("异常通知:afterThrowingPrintLog,异常是:"+e); } @After("pointcut()") public void afterPrintLog(){ System.out.println("最终通知:afterPrintLog"); } /** * 环绕通知 * @param pjp * @return */ @Around("pointcut()") public Object aroundPrintLog(ProceedingJoinPoint pjp){ //定义返回值 Object rtValue = null; try{ //前置通知 System.out.println("前置通知"); //1.获取参数 Object[] args = pjp.getArgs(); //2.执⾏切⼊点⽅法 rtValue = pjp.proceed(args); //后置通知 System.out.println("后置通知"); }catch (Throwable t){ //异常通知 System.out.println("异常通知"); t.printStackTrace(); }finally { //最终通知 System.out.println("最终通知"); } return rtValue; } } 复制代码
**编程式事务:**在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务 **声明式事务:**通过xml或者注解配置的⽅式达到事务控制的⽬的,叫做声明式事务
**原⼦性(Atomicity):**原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。
是从 操作 的角度描述的
⼀致性(Consistency):事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。
从 数据 的角度描述
**隔离性(Isolation):**事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务, 每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。
**持久性(Durability):**持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障 也不应该对其有任何影响。
问题:
数据库四种隔离级别:
Serializable(串⾏化):可避免脏读、不可重复读、虚读情况的发⽣。(串⾏化) 最⾼
Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 第⼆
该机制下会对要update的⾏进⾏加锁
Read committed(读已提交):可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。 第三
Read uncommitted(读未提交):最低级别,以上情况均⽆法保证。(读未提交) 最低
注意:级别依次升⾼,效率依次降低
MySQL的默认隔离级别是:REPEATABLE READ
常用前两条(加粗)
PROPAGATION_REQUIRED | 如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中, 加⼊到这个事务中。这是最常⻅的选择。 |
---|---|
PROPAGATION_SUPPORTS | ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。 |
PROPAGATION_MANDATORY | 使⽤当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则 执⾏与PROPAGATION_REQUIRED类似的操作。 |
注:与AOP配置类似,配置<tx:advice>
开启spring对注解事务的⽀持
<tx:annotation-driven transactionmanager="transactionManager"/>
或 @EnableTransactionManagement
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean 调⽤ org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization 调⽤ org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization(后置处理器AbstractAutoProxyCreator完成bean代理对象创建) 调⽤ org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary 调⽤ org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy(在这⼀步把委托对象的aop增强和通⽤拦截进⾏合并,最终给代理对象) 调⽤ org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy 调⽤ org.springframework.aop.framework.CglibAopProxy#getProxy(java.lang.ClassLoader ) 复制代码
@EnableTransactionManagement 注解 1)通过@import引⼊了TransactionManagementConfigurationSelector类 它的selectImports⽅法导⼊了另外两个类:AutoProxyRegistrar和ProxyTransactionManagementConfiguration 2)AutoProxyRegistrar类分析 ⽅法registerBeanDefinitions中,引⼊了其他类,通过AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)引⼊ InfrastructureAdvisorAutoProxyCreator,它继承了AbstractAutoProxyCreator,是⼀个后置处理器类 3)ProxyTransactionManagementConfiguration 是⼀个添加了@Configuration注解的配置类 (注册bean) 注册事务增强器(注⼊属性解析器、事务拦截器) 属性解析器:AnnotationTransactionAttributeSource,内部持有了⼀个解析器集合Set<TransactionAnnotationParser> annotationParsers;具体使⽤的是SpringTransactionAnnotationParser解析器,⽤来解析@Transactional的事务属性 事务拦截器:TransactionInterceptor实现了MethodInterceptor接⼝,该通⽤拦截会在产⽣代理对象之前和aop增强合并,最终⼀起影响到代理对象 TransactionInterceptor的invoke⽅法中invokeWithinTransaction会触发原有业 务逻辑调⽤(增强事务) 复制代码