Spring
- 因为最近要重回业务的怀抱,因而重新再看一遍Spring的内容刻不容缓
目标
- 对Spring架构上,流程上的分析
- 对于实用点的总结,关于这块,每部分都会有部分总结,然后最后会有一个总的总结
- 对于一些Spring关键点源码分析
- 对Spring设计上的一些分析
- framework is simple,idea is the fucking key
TODO
- AOP各种切面示例代码编写
- AOP中JDK反射原理,CGLIB字节码技术原理(关于字节码的原理可以浅一点)
BeanFactory 和ApplicationContext
额外的延伸点
:
- 关于父子容器的概念:通过HierarchicalBeanFactory这个子类(这个只是Spring继承体系中的一部分),可以实现子容器可以访问父容器,但是父容器不能访问子容器,controller层是子容器,视图层可以访问业务层和持久层的bean,但是相反则不行,
既controller可以访问dao,service,而dao,service无法访问controller
,就是说可以以下犯上,但是不可以倚老卖老
BeanFactory
- 内部是一个hashMap实现的缓存器
bean的生命周期
- 大体流程图为:
- 大体流程图为:
ApplicationContext
ApplicationContext相比BeanFactory的优化
:- 对于所有的bean,当初次加载的时候就会全部加载到容器中
- 新增了
BeanFatoryPostProcessor接口
,作用在于初始化bean之前,先会通过这些工厂后处理器对
配置信息进行加工处理,如有:CustomEditorConfigurer,
PorpertyPlcaeholderConfigurer等,但是注意这些都是工厂后处理器,仅在ApplicationContext初始化的时候调用一次 - ApplicationContext会利用
反射技术自动识别出定义的BeanPostProcessor,InstantiationAwareBeanPostProcessor和BeanPostProcessor
并且自定注册到容器中,而BeanFactory则需要通过代码手动注册(addBeanPostProcessor)
- 对于ApplicationContext的生命周期而言,其实与BeanFactory差不多,就只是多了几个工厂后置处理器而已(
既在ApplicationContext中只需要要将实现BeanPostProcessor接口的类标志为bean即可,他加载过后会自动扫描的
) - ApplicationContext bean的生命周期与BeanFactory相比多了ApplicationContextAware和工厂后处理器而已:
生命周期接口的分类:
bean自身的方法
:- init-method指定的方法或者是@PostConstruct指定的方法
- 构造方法,或者是自身的setter方法
bean级生命周期接口
: 针对的是bean对象- BeanNameAware: 设定bean的名称
- BeanFactoryAware: 持有BeanFactory引用
- ApplicationContextAware: 持有ApplicationContext引用
- InitializingBean: 既afterProperties方法,当变量设置完毕之后会调用
- DisposablebBean: 就是当销毁时候会调用的方法,@PreDestroy
容器级生命周期接口:
: 针对的是所有的bean,既所有的bean都会执行某种操作,当然也可以自编写后置处理器,使得只对特殊的bean感兴趣,全局性的- BeanPostProcessor 接口
- InstantationAwareBeanPostProcessor
工厂后置处理器
: 既实现了BeanFactoryPostProcessor接口的类,当加载完配置文件之后立马会调用wip待完善
- CustomerAutowireConfigurer
- ConfigurationClassPostProcessor
WebApplicationContext
- 作用:
wip
专门用于web,允许从web的根目录文件中装载配置文件从而初始化
,可以获得ServletContext的引用
- 如何使用: 通过WebApplicationContextUtils.getWebApplicationContext(servletContext)获取WebApplicationContext,当然本身也可以通过ApplicationContextAware接口获取ApplicationContext的引用
- 区别:
- ApplicationContext是BeanFactory的一个子类(
wip
) - BeanFactory加载bean的时候,是只有get 的时候才会触发加载,而ApplicationContext则是
初始化上下文的时候就加载了所有的bean信息
- ApplicationContext是BeanFactory的一个子类(
DefaultListableBeanFactory
- 为什么要将这个单独的也抽出来讲解呢,因为个人是挺喜欢造轮子的,而当涉及到SpringCloud中的@FeignClient,@Mapper等空接口的时候,如何动态注入呢,
可以通过DefaultListableBeanFactory动态注入
,当然其实也是可以通过BeanPostProcessor扫描让后发现指定注解之后生成代理类代替的 - 作用:
- Spring容器启动阶段,动态注入自定的bean,并且保证能被aop所加强
- 如何使用:
- 实现
BeanFactoryPostProcessor接口
WIP
- 实现
BeanFactory和ApplicationContext部分总结:
- 前者是心脏,后者是一个完整的人
- 关于生命周期,记住核心的一个:
BeanPostProcessor这个接口,aop的功能就是通过他来实现的,如果我们想创建工具,或者aop中通过this 使得aop|事务生效可以通过这个借口来实现代理
- BeanFactory是只有
初次getBean的时候才会注册bean
,而ApplicationContext是初始化的时候就会加载所有的bean
- bean生命周期中的关于destroy-method和init-method 其实这是对于xml配置而言的,而对于JavaConfig而言就是@PostConstruct和@PreDestroy,既
init-method=@PostConstruct;destroy-method=@PreDestroy
容器事件
- 核心的类: EventObject类(描述事件)和EventListener接口(监听事件)
- 容器事件的核心三要素:
事件源
: 既EventObject内部有一个object的source对象,代表源对象,每个EventObject必须有一个source对象事件监听器注册表
: 既保存事件的容器,当事件发生的时候就会notify这些监听器事件广播器
: 既通知监听器执行任务,起到桥梁的作用
- Spring事件类结构:
事件类
(ApplicationEvent): 通过内部的source指定事件源- 类层次结构图为:
可以发现顶级类是AppEvent,
- ApplicationContextEvent: 容器事件:有
启动
,刷新
,停止
,关闭
事件 - RequestHandleEvent: 与web应用相关的事件,当http请求到来之后会产生该事件,
只有在web.xml中定义了DispatcherServlet时才会产生该事件
- 类层次结构图为:
事件接口
:- 类层次结构图为: 可以发现顶级父类为
EventListener
- 类层次结构图为: 可以发现顶级父类为
SmartApplicationListener
接口3.0新增: 作用:只会针对于某些特定类型的事件做出响应
,通过boolean supportsEventType(Class<? extends ApplicationEvent> event)判断GenericApplicationListener
4.2新增,不再仅仅只支持ApplicationEvent,既事件不再需要继承ApplicationEvent,直接泛型即可,原理就是通过boolean supportsEventType(ResolveType type) 这个ResolveType可以获取到泛型的实际类型信息
-
事件广播器
:- 顶级接口是:
ApplicationEventMulticaster
其抽象类为: AbstarctApplicationEventMuliticaster 其子类为SimpleApplicationEventMulticaster
- 顶级接口是:
-
如何使用呢:
- ApplicationContext,加载完配置文件之后会通过BeanFactoryProcessor从BeanDefinitionRegistry中找出实现了某些接口的bean,因而我们只需要:
- 自定义类实现ApplicationEventMulticaster(
这一步可以没有,如果没有的话会默认使用SimpleApplicationEventMulticaster
) - 自定义实现ApplicationListener
- 自定义继承ApplicationEvent或者是ApplicationContextEvent(只指容器事件),
当然这一步我们是可以直接使用的的,不过这样的话,第二步中就不应该是实现ApplicationListener了,而是继承GenericApplicationListener然后复写supportsEventType来特指这个泛型了
- 当然最终要的一步是,以上的所有都需要注册为bean (或者是@bean等)
- 自定义类实现ApplicationEventMulticaster(
- ApplicationContext,加载完配置文件之后会通过BeanFactoryProcessor从BeanDefinitionRegistry中找出实现了某些接口的bean,因而我们只需要:
容器事件部分总结
核心就是
- 事件源:EventObject
- 事件处理者(监听器对象): EventListener (但我们
大多直接使用ApplicationListener即可
)- 事件广播器: 顶级是ApplicationListenerMulticaster (
这个我们一般不需要去自定义,如果无自定义,Spring默认会使用SimpleApplicaitonListenerMulticaster
)
Bean
-
Bean配置信息在ioc容器中需要存在这些信息:
- bean的实现类
- bean的属性信息(既内容)
- bean的依赖关系(会有循环依赖的问题)
- bean的行为配置(如生命周期或者是回调函数)
-
bean在ioc容器中是以
BeanDefinition
的形式存在的,IOC容器+Bean实现类+Bean配置信息+应用程序的关系如图:
流程为:编写配置
->启动容器->扫描配置类,生成beanDefinitionRegistry注册所有的beanDefinition
->通过beaDefinition实现具体的类
->将生成的bean放到缓存池中(ioc容器)
-
bean配置中有用的点:
parent属性的作用
:,这点在实际项目中遇到过,因为命令模式模板模式往往要抽象类的作用,每个命令都是bean,多了肯定会冗余,因而parent的作用就体现了:
-
bean的模式: 单例(singleton,prototype)模式,如果使用的是WebApplicationContext则还有(request,session)等模式
<bean id="parentObserver"
class="com.dlxy.config.DlxyObservervable">
<property name="obs">
<list>
<ref bean="userRecordObserver" />
</list>
</property>
</bean>
<bean id="userWrappedService"
class="com.dlxy.service.impl.UserWrappedServiceImpl"
parent="parentObserver" />
<bean id="titleWrappedServie"
class="com.dlxy.service.impl.TitleWrappedServiceImpl"
parent="parentObserver" />
<bean id="articleWrappedservice"
class="com.dlxy.service.impl.ArticleWrappedServiceObservableImpl"
parent="parentObserver" />
<bean id="pictureWrappedService"
class="com.dlxy.service.impl.PictureWrappedServiceObservableImpl"
parent="parentObserver" />
这样通过parent属性,就不需要为每个bean都单独的设置list中的属性了
疑惑点:
- WebApplicationContext与servletContext的关联,或者说这2者有什么联系:
- 首先问题先拆分:
- 什么是ServletContext,及其作用,以及何时初始化
-
什么是servletContext
: ServletContext 是一个全局的类,可以认为是共享的数据区
-
ServletContext的作用
: 存放公共配置类,既在<web.xml>中由 < context-param>标签包裹的数据,当然这里额外延伸一点配置问题
:- 在web.xml中可以配置servlet的共享数据
<context-param></context-param> 这种方式可以通过getServletContext().getInitParameter("param1")获取值, 并且是共享的,所有的servlet都可以访问
- 也可以配置某个servlet的特定参数:
<init-param> <param-name>param1</param-name> <param-value>avalible in servlet init()</param-value> </init-param> 只能在servlet中通过this.getInitParameter("param1")获取
-
ServletContext何时初始化
: 当Servlet容器第一次启动的时候就会实例化一个ServletContext -
如何使用
: 可以通过ServletContextAware接口获取到SerletContext的引用,并且由此我们可以发现Spring设计上的一个特点,Aware接口
-
什么是WebApplicationContext
:什么是WebApplicationContext
: WebApplicationContext是ApplicationContext的一个子类,这是专门为Web准备的,为何这么说,它多了额外的属性:bean的scope属性:如request,session等
,其顶级接口又是BeanFactory,因而可以认为是ioc容器
,webApplicationContext无法更通俗理解,它有多个实现类,其中有XmlWebApplicationContext和AnnotationConfigWebApplicationContextWebApplicationContext的作用:
很明显,既是BeanFactory的作用,作用是存放所有的bean
何时初始化
: 当servlet执行init方法之后,会触发ContextLoadListener,在ContextLoadListener中会初始化WebApplicationContext(通过event.getServletContext())
- 总结:
- ServletContext是
共有数据
,存放Servlet间共享的数据 - WebApplicationcontext是ioc容器,Servlet中内部的业务总需要用到bean是吧(毫无争议),为啥ServletContext中要引用WebApplicationContext呢,而不是相反呢,
可以这么理解:先后问题和职责问题:
- 因为先有ServletContext后有WebApplicationContext ioc容器
- 职责问题: ServletContext的职责就是
数据共享,存放所有的配置信息
,那么所有bean 的引用当然要存放在这咯
- ServletContext是
- Spring默认bean是singleton模式,适合无状态或者状态不可变的对象,但是dao类会持有connection,这点是如何避免的
- dao是orm层概念,用于与数据库交互,也就意味着需要持有connection这个成员,也就意味着是有状态的,如何避免呢,答案就是通过
ThreadLocal
- dao是orm层概念,用于与数据库交互,也就意味着需要持有connection这个成员,也就意味着是有状态的,如何避免呢,答案就是通过
Spring AOP
- 首先,什么是aop,aop既
面向切面编程
,竖线逻辑上插入 - AOP中的术语:
- joinpoint: 既连接点,可以认为
就是类中的所有方法都是连接点
- pointcut: 切点,
连接点的细分
,并不是所有的连接点都会是切点,只会针对特定的连接点作为切点(既进行额外的加工) - advice: 增强,既对切点的增强,字面翻译即可,并且有BeforeAdvice,AfterReturningAdvice,ThrowsAdvice等方法
- joinpoint: 既连接点,可以认为
- AOP增强(织入的方式):
- 编译器织入: 要求有特殊的Java编译器
- 类装载期织入: 特殊的类加载器
- 动态代理织入: 既运行期,通过动态代理技术增强
- AOP的实现者有如下:
- AspectJ:
编译期
提供横切代码的逻辑,有一个专门的编译器和特殊的类加载器 - SpringAOP:
运行期
通过代理方式增强,侧重于ico与aop的结合
- AspectJ:
动态代理
- JDK动态代理:
- 核心: InvocationHandler,Proxy
- 原理: 通过
反射
- 如何使用:
- 自定义类实现InvocationHandler接口
- 通过Proxy.newProxyInstance(classloader,interfaces,oldImpl);生成的类既是通过代理之后的类
WIP:代码演示
- cglib动态代理:
- 核心: MethodInterceptor,Enhancer
- 原理: 通过底层字节码技术
- 如何使用:
- 自定义类实现MethodInterceptor接口
- 通过enhancer设置超类和回调函数,然后enhancer.create返回的就是代理类
WIP:代码演示
- JDK动态代理和CGLIB动态代理的区别:
适用性
:cglib更加适用,jdk基于接口,既被代理的类必须有实现的接口才可以,而cglib则是衍生出子类来实现代理
原理
:jdk是反射
,cglib原理是字节码技术
性能
上cglib性能更高
Spring 的增强
- 在上述中,我们每次进行横切逻辑都需要手动编写,那么我们
完全可以抽出接口来简化编程
,Spring有如下增强类:- 前置增强: BeforeAdvice (当方法执行前的增强)
- 后置增强: AfterReturingAdvice (当方法执行完毕之后的增强)
- 环绕增强: MethodInterceptor (
范围最广,不仅能充当前置,后置,也能充当异常抛出增强
) - 异常抛出增强: ThrowsAdvice (抛出异常后进行处理)
- 引介增强: IntroductionInterceptor:(既额外添加新的方法和属性)
- 如何使用呢:
- 其实与上述是差不多一致的,当我们使用CGLIB的时候,会通过enhancer,MethdoInterceptor实现
- 核心类: ProxyFactory (Spring自带的)
- 实现BeforeAdvice或者是上述的任意增强类
- 通过ProxyFactory指定target对象(这个对象是实际的原先的对象)
- 通过ProxyFactory添加advice增强(既第一步实现类)
- 通过ProxyFactory生成proxy : pf.getProxy() 即可
WIP:代码演示
- 关于ProxyFactory这个的使用: 内部有Cglib2AopProxy和JdkDynamicAopProxy两个final 实现类,如果setInterfaces指定接口,则会使用Jdk否则的话使用Cglib,
setOptimize(true)会启用优化代理,这样就算是接口也会是使用cglib
- 是
Spring的切面
-
切面是
切点+增强类型
- 切点又有如下切点:
- 静态方法切点:
StaticMethodMatcherPointcut
是静态方法切点的抽象基类,- 有NameMatchMethodPointcut : 简单字符串匹配方法签名
- 和AbstracRegexpMethodPointcut: 正则匹配的方式
WIP待补,怎么进行匹配的待补
动态方法切点
: DynamicMethodMatcherPointcut ,是抽象基类WIP待补,同上
注解切点
: AnnotationMatchingPointcut: 支持在bean中通过注解标签定义的切点表达式切点
: ExpressionPointcut接口 ,为了支持AspectJ切点表达式语法而定义的接口流程切点
:WIP
复合切点
: ComposablePointcut 为创建多个切点而提供的方便操作类,WIP需要配合代码理解,虽然可以简单的认为与以前的想法一致
- 静态方法切点:
- 切点又有如下切点:
-
切面类型的分类:
Advisor
: 一般切面,仅仅只是包含了Advice(增强类型)PointcutAdvisor
: 代表的是具有切点的切面
,包含Advice和Pointcut2个类- 这是一个接口,旗下有多个实现类,对应的实现类与切点的类型对应
IntroductionAdvisor
: 代表引介切面
,对应的advisor是引介增强类型- 怎么使用呢:
- 编写自己的业务逻辑
- 编写增强类型(是前置呢还是后置还是环绕等)
- 自定义切面(用于捕获对哪些切点感兴趣)
- ProxyFactory 设置target(既业务逻辑实现类) ,设置interface| 设置optimize优化 ;
添加切面
- ProxyFactory生成代理对象
AOP的部分总结:
- Joinpoint(连接点)含有: 通常就是某个类的所有方法
- Advice(增强)含有: 包含了
横切代码
和连接点的信息(包含了这个类的所有方法) - Pointcut(切点,连接点的细化)含有:具体方法名称,方法参数信息等
AOP疑惑点:
- JDK动态代理如果是一个空接口的话怎么办,如@FeignClient所标识的,但是没有fallback类
- JDK动态代理的原理,反射,反射又是基于什么原理的
- 什么是切面,什么是增强类型:
增强类型
: 增强类型是指 前置增强(BeforeAdvice)还是后置增强(AfterReturningAdvice)还是环绕增强等(既发生增强所处的时机)切面
是切点+增强类型
Spring配置(专门用于讲解配置):
配置方式:
- 通过xml配置
- 通过注解@Componnet配置
- 通过JavaConfig配置
- 省略groovy的方式
注意
:
- 在非SpringBoot下,如果使用的是JavaConfig的形式,则web.xml中的参数需要指定为
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</context-param>
然后对于原先的contextConfigLocation则是指向具体的类
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.test.config.Configuraiton</param-value>
</context-param>
当然你不指定也是没关系的,只要通过 <component-scan basepackages="com.demo">
这样讲配置类扫描进去也是可以的,但是注意要与SpringMVC的配置扫描分开哦,不然会加载两次,血的教训
WebApplicationContext的配置:
-
在web.xml中配置:
,至于作用在上面已经申明了<listener> <listener-class>org.springfarmework.web.context.ContextLoadListener</listener-class> </listener> 这个listener可以获取到<context-param>中的名为contextConfigLocation的值, 至于这个值是可以采用统配符的: classpath:*/spring-*.xml 如: <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-mvc.xml</param-value> </context-param>
日志功能的配置:
注意:
- 日志的配置必须在Spring的配置之前
- 日志的配置可以使用listener或者是servlet
- 如果用listener的话,需要将这个listener
放置在contextConfigListener之前
- 如果用servlet的话,
load-on-startup要设置为1
,优先级别最高
- 如果用listener的话,需要将这个listener
- 核心参数就是log4jConfigLocation
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
- listener方式:
<listener>
<listener-class>Log4jConfigListener</listener-class>
</listener>
- servlet方式:
<servlet>
<servlet-name>log4jConfigSerlvet</servlet-name>
<serlvet-class>org.springframework.web.util.Log4jConfigServlet</servet-class>
<load-on-startup>1</load-on-startup>
</servlet>
设计方面
- 核心容器是通过HashMap来设置的
wip:key是什么类型
,当然有一大堆配置类,配置类线性结构,通过order排序,然后顺序调用 - Spring 的设计基本都是一个主接口,然后衍生出一大堆的子接口,或者是抽象实现类,然后在抽象实现类中再定义一些额外的方法,从而使得丰富多样
- Spring Aware接口的作用: Spring因为有些
单例的长生命周期的对象
,因而都会开放其Aware接口,用于开发者获取其引用,如ApplicaitonContextAware接口,ServletContextAware接口,因而,在我们以后的设计中也大可以这样通过aware接口获取长生命周期框架对象的引用