Spring浅谈 - Go语言中文社区

Spring浅谈


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信息

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,因而我们只需要:
      1. 自定义类实现ApplicationEventMulticaster(这一步可以没有,如果没有的话会默认使用SimpleApplicationEventMulticaster)
      2. 自定义实现ApplicationListener
      3. 自定义继承ApplicationEvent或者是ApplicationContextEvent(只指容器事件),当然这一步我们是可以直接使用的的,不过这样的话,第二步中就不应该是实现ApplicationListener了,而是继承GenericApplicationListener然后复写supportsEventType来特指这个泛型了
      4. 当然最终要的一步是,以上的所有都需要注册为bean (或者是@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和AnnotationConfigWebApplicationContext
      • WebApplicationContext的作用: 很明显,既是BeanFactory的作用,作用是存放所有的bean
      • 何时初始化: 当servlet执行init方法之后,会触发ContextLoadListener,在ContextLoadListener中会初始化WebApplicationContext(通过event.getServletContext())
    • 总结:
      • ServletContext是共有数据,存放Servlet间共享的数据
      • WebApplicationcontext是ioc容器,Servlet中内部的业务总需要用到bean是吧(毫无争议),为啥ServletContext中要引用WebApplicationContext呢,而不是相反呢,可以这么理解:先后问题和职责问题:
        • 因为先有ServletContext后有WebApplicationContext ioc容器
        • 职责问题: ServletContext的职责就是数据共享,存放所有的配置信息,那么所有bean 的引用当然要存放在这咯
  • Spring默认bean是singleton模式,适合无状态或者状态不可变的对象,但是dao类会持有connection,这点是如何避免的
    • dao是orm层概念,用于与数据库交互,也就意味着需要持有connection这个成员,也就意味着是有状态的,如何避免呢,答案就是通过ThreadLocal

Spring AOP

  • 首先,什么是aop,aop既面向切面编程,竖线逻辑上插入
  • AOP中的术语:
    • joinpoint: 既连接点,可以认为就是类中的所有方法都是连接点
    • pointcut: 切点,连接点的细分,并不是所有的连接点都会是切点,只会针对特定的连接点作为切点(既进行额外的加工)
    • advice: 增强,既对切点的增强,字面翻译即可,并且有BeforeAdvice,AfterReturningAdvice,ThrowsAdvice等方法
  • AOP增强(织入的方式):
    • 编译器织入: 要求有特殊的Java编译器
    • 类装载期织入: 特殊的类加载器
    • 动态代理织入: 既运行期,通过动态代理技术增强
  • AOP的实现者有如下:
    • AspectJ: 编译期提供横切代码的逻辑,有一个专门的编译器和特殊的类加载器
    • SpringAOP: 运行期通过代理方式增强,侧重于ico与aop的结合
动态代理
  1. JDK动态代理:
    • 核心: InvocationHandler,Proxy
    • 原理: 通过反射
    • 如何使用:
      1. 自定义类实现InvocationHandler接口
      2. 通过Proxy.newProxyInstance(classloader,interfaces,oldImpl);生成的类既是通过代理之后的类WIP:代码演示
  2. cglib动态代理:
    • 核心: MethodInterceptor,Enhancer
    • 原理: 通过底层字节码技术
    • 如何使用:
      1. 自定义类实现MethodInterceptor接口
      2. 通过enhancer设置超类和回调函数,然后enhancer.create返回的就是代理类WIP:代码演示
  3. JDK动态代理和CGLIB动态代理的区别:
    • 适用性:cglib更加适用,jdk基于接口,既被代理的类必须有实现的接口才可以,而cglib则是衍生出子类来实现代理
    • 原理:jdk是反射,cglib原理是字节码技术
    • 性能上cglib性能更高

Spring 的增强

  • 在上述中,我们每次进行横切逻辑都需要手动编写,那么我们完全可以抽出接口来简化编程,Spring有如下增强类:
    • 前置增强: BeforeAdvice (当方法执行前的增强)
    • 后置增强: AfterReturingAdvice (当方法执行完毕之后的增强)
    • 环绕增强: MethodInterceptor (范围最广,不仅能充当前置,后置,也能充当异常抛出增强)
    • 异常抛出增强: ThrowsAdvice (抛出异常后进行处理)
    • 引介增强: IntroductionInterceptor:(既额外添加新的方法和属性)
  • 如何使用呢:
    • 其实与上述是差不多一致的,当我们使用CGLIB的时候,会通过enhancer,MethdoInterceptor实现
    • 核心类: ProxyFactory (Spring自带的)
    1. 实现BeforeAdvice或者是上述的任意增强类
    2. 通过ProxyFactory指定target对象(这个对象是实际的原先的对象)
    3. 通过ProxyFactory添加advice增强(既第一步实现类)
    4. 通过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是引介增强类型
    • 怎么使用呢:
      1. 编写自己的业务逻辑
      2. 编写增强类型(是前置呢还是后置还是环绕等)
      3. 自定义切面(用于捕获对哪些切点感兴趣)
      4. ProxyFactory 设置target(既业务逻辑实现类) ,设置interface| 设置optimize优化 ; 添加切面
      5. 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,优先级别最高
  • 核心参数就是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接口获取长生命周期框架对象的引用
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Coder_Joker/article/details/87894352
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢