Spring学习总结 IoC AOP 事务管理

spring也学了很久了,IoC、AOP似乎也已经是老生常谈,但是初学者的脑子里往往学完就忘了,概念也很混乱,因此这里将自己对spring的理解系统的整理了一遍,算是对spring的学习总结。

  1. Spring是什么
Spring是一个开源的轻量级javaSE/javaEE开发应用框架
 
它主要可以做以下这些事情:
(1)帮助我们管理对象及其依赖(IoC/DI)
         一个应用程序是由一组互相协调的对象组成的,在开发应用时除了要开发业务逻辑,还要关注如何协调这些对象,并且要保证对象之间的低耦合、高内聚。
        因此就有了这样一个需求:如果有一个框架可以帮助我们去创建对象并且管理对象之间的依赖关系就好了。
        这就是Spring最开始的想法,它最核心的东西就是这个,IoC/DI
        或许工厂设计模式也可以帮我们去创建对象啊,但这又需要我们去创建工厂类,又增加了我们的负担
 
(2)提供日志、安全控制、性能统计等面向切面的能力(AOP)
         日志、安全控制、性能统计这些代码可能在很多对象中会用到,如果写在对象中,那就有大量重复的代码
         而且,像性能统计这种只在需要的时候用一下,正式上线的时候都要去掉的,如果写在对象里面,那就很麻烦,要去改代码,重新编译这样子
        有了Spring的话,利用面向切面的变成方式,将这些重复代码从业务逻辑代码中抽离出来,然后在需要这些功能的地方动态的添加这些功能,只需要修改配置,不需要添加额外的代码
 
      Spring的AOP是基于动态代理的,或许有人说直接用动态代理不行吗?如果你不嫌麻烦的话,毕竟自己去写代码,又要多很多的代理类。
 
(3)帮助管理事务
        Spring的事务管理是基于AOP的,我们只需要获取连接,执行SQL,其他的都交给Spring来完成
 
(4)与第三方数据访问框架(Hibernate、JPA、MyBatis)无缝集成,自己也有一套JDBC Template来访问数据库
 
(5)与第三方web框架无缝集成,自己也提供了Spring MVC框架 来搭建表现层
 
(6)可以方便的与更多技术整合,如缓存框架、java mail、任务调度等
 
 
  1. Spring的优点
(1)轻量级容器
        以集中式、自动化的方式进行对象创建和装配,负责创建和装配,管理对象生命周期。非侵入式,POJO开发,灵活快速,核心jar包很小,不依赖服务器,方便部署
(2)AOP
 
(3)简单的事务管理
 
(4)JDBC以及ORM支持
mybatis
(5)灵活的WEb层支持
spring mvc
(6)简化各种技术集成
            java mail  JMX  JMS........
总之,Spring能够简化开发,帮助我们创建、管理对象,提供AOP,帮助管理事务,还可以作为一个超级粘合平台,将很多的技术整合在一起。
 
 
 
  1. Spring怎么用
 
我们将从下向上学习Spring的使用,首先是IoC/DI,然后AOP,最后看一下SPring中的事务管理
至于Spring与web层的整合(Spring MVC),Spring与DAO层的整合(MyBatis)这里就不详细说了,在具体的框架中再来看
 
3.1 IoC/DI
IoC——控制反转,是一种设计思想,我们要用一个对象时,不需要自己去创建,只需要告诉Spring的容器,由容器为我们创建
IoC的好处是,对象与对象之间是松散耦合的,方便测试,利于功能复用,更重要的是把程序的整体结构变得十分灵活
 
DI跟IoC的关系十分密切,它的意思是依赖注入,即IoC容器可以动态的将一个对象注入到另一个对象中
依赖注入的方法有三种:接口注入(侵入性,不推荐)、构造注入、setter注入
 
了解完IoC和DI的基础概念,我们可以看出,这里的关键就是IoC容器
控制反转,将对象控制权交给容器,由容器为我们创建、管理对象,包括生命周期、对象间的依赖关系
依赖注入中,也是由容器为对象注入
 
那么我们就有一个疑问:容器是如何来管理对象之间的依赖关系的呢?它怎么知道程序中的A对象要依赖一个B对象?
Spring中有三种方法来告诉容器:
(1)直接编码(最基本的方式,底层)
        在容器启动之前,通过程序编码的方式将需要的对象注册到容器中,并且明确相互之间的依赖关系
        这种方法比较复杂,在实际的开发中不会用到,因为这就违背了Spring的初衷了,如果需要这么繁琐的编程环节,还要框架有什么用呢?
        
        但是对象之间的依赖关系必然是要落实到代码的,也就是说,不论是用下面提到的配置文件还是注解,其本质都是要编码的,只不过它们封装了编码的过程,不需要我们自己去写代码,只要按照Spring的规范给出配置文件或者添加注解,框架就会为我们生成代码,把对象注册到容器中
        所以直接编码的管理方式是Spring容器的本质,是比较底层的东西,如果只是要用Spring,那么你直接用下面的配置文件和注解就好了,但是你要去理解Spring,那么直接编码这一块也是要去看去理解的
(2)配置文件,如XML
       最常见的
(3)注解
 
 
上面我们其实都在讲一些理论上的东西,比如什么是IoC,Spring里的IoC是怎么样的
接下来我们就从Spring的IoC容器开始,看一看Spring里的IoC和DI到底是怎么用的,以及深入的了解是怎么一回事
 
Spring框架为我们提供了两种容器类型: BeanFactory  和  ApplicationContext
BeanFactory是最基础的IoC容器,它提供了完成的IoC服务支持,默认采用延迟初始化策略 lazy-load懒加载(当对象被访问到时,才会对改对象进行初始化和依赖注入)
因此相对来说,其需要的资源较少,初期启动速度较快
ApplicationContext是在BeanFactory的基础上构建的,在启动后默认初始化对象并绑定完成,因此初期启动速度较慢
 
3.1.1 BeanFactory
顾名思义,BeanFactory就是一个生产Bean的工厂,程序中的对象就是一个Bean,将它放到容器中,就可以对它进行管理
 
对于BeanFactory的学习就分为两个部分:使用和原理  
那么就先从BeanFactory的使用开始看,要使用一个容器,就必须把它按照我们程序的需要配置好
在Spring里,就是要把程序中的对象注册到容器中,让容器来管理它们
 
注册的方法有三种:直接编码  、  配置文件  、  注解
(1)直接编码
直接编码是方式是最底层最基础的,因为其他的方法本质上都要落实到编码,只是它们封装了后,不需要自己编码,而是交给框架自动来生成,为了了解容器注册的本质,我们需要来看一看直接编码中Spring是如何把一个个的对象注册到容器里面,并且管理的
 
 
上图就是Spring中将对象注册到容器中用到的类
具体的代码要看的话在P26
我们这里就大致写一下伪代码,了解它注册的大致流程
1> new DefaultListableBeanFactory
     创建一个注册bean的类
2> new BeanDefinition  
     把要注册的对象的信息都存到保存对象信息的类中
3> DefaultListableBeanFactory .registerBeanDefinition( BeanDefinition )  
     将对象注册到容器中
4> BeanDefinition .setConstructorArugmentValue()  /   BeanDefinition .setPropertyValues()
     指定对象之间的依赖关系,通过构造注入 / setter方法注入
5> 至此就获得了一个配置好的容器了
 
 
(2)XML配置文件方式
本质上也是要落实到直接编码的,只是封装了一层,让框架根据我们给出的配置文件来自动生成代码
XML配置文件就相当于是我们给容器的对象的信息,所以跟直接编码相比要多一步:要先去读取配置文件,,然后存到BeanDefinition中,之后的流程就跟直接编码一样了
 
BeanDefinitionReader做了绝大部分的工作
 
这里还有一个点,那就是XML配置文件是怎么写的呢?
因为XML的配置方式是 最全面、最强大的容器信息管理方式,所以里面的书写规范是非常复杂的
但我们学起来其实很简单,因为常用的不多
主要用到的配置点就是:注册bean  、  说明bean之间的依赖关系  、  bean的作用域
这些都十分好理解,找一个例子看一看就很清楚了
比如注册bean时就用<beans>和<bean>这两个标签
bean之间的相互依赖则通过<construntor-arg>和<property>两个标签来实现,分别对应的是构造注入和setter注入,当然这里面还有一些细节,比如属性的顺序用index,ref标签等
bean的作用域要记住有singleton、prototype,前者在容器中只会存在一个对象实例,后者则是每次请求都会生成新的对象实例
 
一般的bean注册就用上面的就够了,然而还有一个特殊的情况,那就是一些第三方的类库,如果没有源码,没有办法注册到容器里怎么办?
方案就是用工厂模式
静态工厂方法
自己定义一个工厂类,里面有一个静态的方法返回第三方类库的那个对象
在XML注册的方法就是:
<bean id="xxx" class="工厂类的路径"  factory-method = "工厂类的静态方法名"/>
 
非静态工厂方法
如果自己定义的工厂类那个方法不是静态的,那么注册的方法就是:
<bean id=“factory"  class="工厂类的路径" >
<bean id="XXX"  factory-bean="factory"  factory-method="工厂类的方法名">
 
FactoryBean
FactoryBean是Spring提供的,相当于是标准用法
 
public interface FactoryBean{
    Object  getObject();
    Class getObjectType();
    boolean isSingleton(); 
}
 
用法是 创建一个第三方类库的工厂类,实现FactoryBean接口,然后就把工厂类注册进去就好了,Spring框架会自动的去获取工厂类生产的对象
 
 
(3)注解方式
需要注意的是,注解方式只适用于spring2.5以及java5以上版本
<context:component-scan  要扫描的包的路径 />
只需要在配置文件中加上上面的一句,Spring就会自动在路径下扫描带有@Component注解的类,然后将其注册到容器中
 
不要忘记开启注解扫描 <context:annotation-config>
 
在程序中需要用到某个类时,只需要用@Autowired注解,框架就会自动把对象注入进去
 
 
---------上面就是BeanFactory的基本用法,那下面就来看看BeanFactory背后的故事,它的原理是怎么样的---------------------------
分为两个部分:容器的启动  和  bean的实例化(bean的生命周期)
 
 
 
 
容器的启动过程就如上图所示,BeanDefinitionReader加载配置,存到BeanDefinition上,再注册到容器的注册器DefaultListableBeanFactory
这里要关注的就是一个后处理,即注册完之后我们还可以做一些操作
BeanFactoryPostProcessor
容器后处理器,允许我们在容器启动后,对注册到容器的BeanDefinition信息做修改
BeanFactory中需要手动去配置后处理器,ApplicationContext中则十分简单,只需要把后处理器的实现类放到容器里,容器会自动识别并应用
 
应用的场景是这样的:因为某些系统管理的相关信息比较敏感,我们不希望把它们和业务对象的配置信息混杂到一起,避免维护时出错。因此就可以把诸如数据库连接、邮件服务器等信息单独配置到一个properties文件中来加载
 
 
 
 
BeanFactory容器启动完毕后并不会立即实例化bean对象,只有当它被请求时才会触发bean的实例化阶段
 
 
实例化的过程中有以下几点要注意的:
(1)容器内部采用策略模式来决定采用何种方式初始化bean,可采用反射和CGLIB的方法
(2)bean实例化完成以后,返回的是一个 BeanWrapper实例,它的作用是对bean进行包裹,从而方便对bean的属性进行设置
        若没有这个包裹对象,直接通过反射来设置对象的属性是很麻烦的啊!
(3)各色aware接口,若bean实现了这些接口,容器会将接口定义中规定的依赖注入到对象实例中
        例如实现BeanFactoryAware,容器就会把自身设置到当前对象实例中
(4)BeanPostProcessor 
            这里要注意对比BeanFactoryPostProcessor,后者是在容器启动阶段发挥作用的
 
        public interface BeanPostProcessor{
                postProcessorBeforeInitialization(Object bean,String beanName) 初始化之前调用
                postProcessorAfterInitialization(Object bean,String beanName)  初始化之后调用
        }
    通过上面两个方法,可以很方便的对原来的对象实例进行扩展增强
    Spring里的AOP就用到了这个类,通过BeanPostProcessor为对象生成代理对象
 
 
 
  3.1.2 ApplicationContext 
        ApplicationContext 是BeanFactory的增强版容器,其基本功能跟上面的保持一致
       扩展的功能主要是:BeanFactoryPostProcessor BeanPostProcessor等特殊bean的自动识别、容器启动后bean的自动实例化、国际化信息支持、容器内事件发布等
 
 
 
3.2 AOP
在学习Spring的AOP之前,我们首先要明确AOP是什么,以及为什么需要AOP
我们知道java是面向对象的语言,OOP是它最大的一个特点,通过封装、继承、多态等概念,建立了一种对象层次结构,用于模拟公共行为的一个集合
然而OOP适合定义纵向的关系,不适合定义横向的关系
如日志、安全控制等代码是分散在所有对象的层次中的,跟对象的核心代码没有什么关系,因此OOP中会导致大量的代码重复,不利于各个模块的重用
因此我们就需要AOP,它利用一种”横切“的技术,将切面横切进对象中。简单来说,AOP将那些跟核心代码无关,但又被对象广泛调用的公共行为封装起来,减少代码的重复度,降低模块间的耦合度。
 
AOP的核心概念:
(1)JointPoint
            连接点
(2)Pointcut
            切入点
(3)Advice
            通知
(4)Aspect
            切面
(5)target
            目标对象
(6)weave
            织入
所以,我们可以说AOP的过程就是, 把切面织入目标对象的切入点的过程
 
 
Spring的AOP在IoC/DI的基础之上,采用动态代理或CGLIB的方式来实现
下面我们先来学习Spring AOP的使用方法,然后再深入原理
 
虽然AOP要做的工作非常多,但大部分都由框架来完成,程序员只需要做三件事
(1)定义目标对象bean
(2)定义切入点
(3)定义通知,即要增强的功能
因此我们要做的其实就是做一个切面(Aspect=pointcut+Advice),剩下的就交给AOP框架,自动的去为目标对象生成代理对象
 
在Spring 1中,AOP的配置十分麻烦,是基于接口的(接口来源于AOP联盟),要定义一个切面就必须去实现那个接口
因此Spring 2引入了AspectJ,简化了AOP的使用
Spring 2中的AOP有两种使用方式: (1)@AspectJ形式 (2)基于Schema配置的XML文件形式
引入AspectJ最大的作用就是允许我们使用POJO的形式来声明相关的Aspect、Advice、Pointcut,并且采用新的Pointcut表达方式,更加简介好用
要注意是是,Spring虽然引入了AspectJ,但其实只是套了个壳子,只是用AspectJ的类库来解析、匹配Pointcut,最终的实现机制还是传统的Spring1AOP架构,说的不好听点就是披着狼皮的羊
我们不是傻子,所以在学习如何使用Spring的AOP时,我们就用Spring 2中的AOP,但后面讲原理时,就不得不去深入底层,学习Spring 1的AOP了
 
(1)@ASpectJ形式
@AspectJ形式的AOP允许我们使用POJO来定义切面,无需实现接口
记住这几个注解:@Aspect  @Pointcut  @Around/@Before/@After
 
下面我们就来定义一个切面类
@Aspect
public class 切面类{
 
    @Pointcut(EL表达式匹配切点,比如:execution(public void * .method)切点就是所有名字为method的方法) 
    public void pointcutName(){} 这个方法是没用的,完全就是为了定义切点
 
 
    @Around(pointcutName) 这里引用切点方法的函数名,表示这个通知是要插入这个切点的
    public void xxx(){
        这里就可以写要增强的功能
        ...
    }
}
 
最后不要忘记,在配置文件中,要把切面类的包路径放进去,让容器开始后自动扫描带有@Aspect的类
扫描到之后就会加到容器中,然后解析出切点和通知,当目标对象被调用时,通过前面讲过的后处理器为目标对象去生成代理对象,就成功的把切面织入到对象的切入点上了
 
注意:织入的过程虽然是自动的,但不要忘记把自动织入器AutoProxyCreator注册到容器中,否则的话,有米没锅还是做不了饭啊
            当容器中有自动织入器时,就会自动检索容器中扫描进来的Aspect类,将其织入到目标对象的切点上
            当然,织入的过程也可以手动来控制,用AspectJProxyFactory,但是既然有自动的,谁还会愿意去手动管理呢,一般情况下用自动的就好了
 
 
 
(2)基于schema的AOP
        spring 1中基于DTD的AOP只需要修改一些配置就可以移植到基于schema的AOP,新的配置方式为AOP功能提供了独有的 命名空间,配置起来将更加轻松
        那么为什么有了@AspectJ形式的AOP还需要一个基于schema的AOP呢?
        这是因为注解是java5才出现的啊,如果你的程序版本太低,又必须POJO形式的AOP,那么就只能用基于schema的XML配置文件来配置AOP
 
        <aop:config >
            <aop:pointcut/>
            <aop:advisor/>
            <aop:aspect/>
        </aop:config>
 
-------------那上面就是如何使用AOP了,下面就来看一看AOP到底是怎么实现的----------------------    
 
这里我们就要深入Spring 1中对AOP功能的实现原理了
 
Pointcut
public interface Pointcut{
    ClassFilter getClassFilter();  类型匹配
    MethodMatcher getMethodMatcher();  方法匹配:静态匹配——不检查参数,最常用;动态匹配——检查方法参数
    Pointcut TRUE = TruePointcut.INSTANCE;
}                                                                                             
上面我们在声明一个Pointcut时好像十分的简单,用execution(匹配表达式),可以快速的定位到目标对象的切点上
然后实际上Spring中底层的Pointcut还是十分麻烦的,我们就简单了解下就好
 
Advice
为了标准化AOP的使用,Spring AOP加入了AOP联盟,因此其Advice的接口都是遵循AOp联盟的规定
(1)Before Advice
增强的方法将在切点方法之前执行
(2)ThrowsAdvice
对系统抛出的异常进行监控
(3)AfterReturningAdvice
在切点方法执行之后,并且可以访问切点方法的返回值、方法、参数等
(4)Around Advice
最强大的advice,可以完成其他通知能完成的事情
 
 
Aspect
Advisor就是Spring 1中的Adpect,它很特殊,只能包含一个Pointcut和一个Advice
 
Advisor是一个大的接口,下来它有两个分支,分别是PointcutAdvisor  和   IntroductionAdvisor,一般我们就关注前者,后者基本不会用到
 
 
 
所以底层中的切面就是这样来定义是:
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice)
 
 
Weaver
有了材料,我们就可以用锅做饭啦!
Aspect=pointcut+advice 拿到切面后,交给织入器,把增强的逻辑织入到切点
可以说,织入的过程是AOp最重要的过程了,一定要好好的理解
 
Spring中的织入器是 ProxyFactory
Spring 的AOP是基于代理的,所以织入切面的过程其实就是生成代理对象的过程
将定义好的切面交给织入器,织入器完成织入后,就返回给我们一个代理对象,通过代理对象去调用方法时,就是增强后的方法
 
new ProxyFactory( )    先new一个织入器出来
ProxyFactory.setTarget( 目标对象 )   将目标对象设置到织入器
ProxyFactory.addAdvisor( 切面 )   将切面设置到织入器
Object proxyObject = ProxyFactory.getProxy( )   织入→返回一个增强后的代理对象
 
我们知道AOP的代理分为 java的动态代理 和 CGLIB的代理,前者要求目标对象必须实现接口,后者是对前者的补充
因此可以对ProxyFactory进行设置选择代理的方式:
ProxyFactory.setInterfaces( 接口 )  就可以告诉织入器,要用java的动态代理
ProxyFactory.setProxyTargetClass( True ) 就选择了CGLIB的代理
 
-------那上面的话就是ProxyFactory简单的使用方法,下面要讲的是它的原理----------------------
 
 
 
 
 
从上图我们可以比较清晰的看清AOP中织入器的设计脉络
AopProxy是AOP中代理的根的接口,它是由AopProxyFactory这个抽象的工厂来生产的(根据AdvisedSupport中提供的信息来生产)
 
ProxyFactory集AopProxy和AdvisedSupport于一身,因此我们既可以通过ProxyFactory来设置代理对象的相关信息,也可以通过它获得代理的对象
 
织入器除了ProxyFactory还有其他两种实现类:AspectJProxyFactory(Spring 2中引入了AdpectJ)和 ProxyFactoryBean(IoC容器中的织入器)
 
通过ProxyFactory我们可以脱离Spring的IoC容器使用AOP,然而将AOP和IoC容器结合起来更好用啊,因为我们可以在容器中对Pointcut和Advice进行管理
 
ProxyFactoryBean是一个用来生成Proxy的FactoryBean,这里我们要回忆前面讲过的FactoryBean:当容器中注册了一个FactoryBean时,获取对象时得到的是工厂生产的对象,而不是工厂对象本身
因此ProxyFactoryBean的使用十分简单,它跟ProxyFatory继承自一个父类,大部分的工作都已经由父类做好了,它只需要去实现FactoryBean接口,然后在getObject()方法中把代理对象返回给容器
 
最后我们再了解下AOP中的自动化织入
因为上面都是需要我们为每一个目标对象去设置代理,系统小的时候还可以承受,一旦有很多很多的类,那么就需要Spring中的自动代理机制
自动代理的本质是前面的BeanPostProcessor
 
 
3.3 Spring中的事务管理
 
Spring中事务管理相关的有三个类:
(1)PlatformTransactionManager  平台事务管理器
        作用是为不同的持久层技术提供不同的事务管理器,如DatasourceTransactionManage,用于Spring JDBC/Mybatis
        
        方法有以下:
        commit 提交事务
        rollback  回滚事务
        TransactionStatus getTransaction(TransactionDefination) 根据事务的定义信息,获取事务的状态信息
 
(2)TransactionDefination   事务定义信息
            用于在Spring中配置事务信息
            有以下几个点可以配置:
            ISOLATION_*  事务的隔离级别,解决并发的问题,5种隔离级别
            PROPAGATION_*  事务的传播行为,解决多个事务之间相互调用的问题,支持7种,记住:REQUIRED  使用同一个事务  REQUIRES_NEW  开启新的事务   NESTED  嵌套事务
            TIMEOUT_DEFAULT  超时时间
            Read-Only  是否只读   
 
(3)TransactionStatus  事务状态信息
            事务执行过程中,某一时间点的状态信息
 
Spring中的事务使用有两种方式:编程式(代码侵入,不用)和声明式(基于AOP实现)
下面我们就来学习以下spring中声明式的事务怎么开启:
因为底层是基于AOP的,所以就是把框架中自带的事务Advice织入到需要事务的地方(切点)
 
spring2中都采用基于schema的配置了,对事务也引入了tx名称空间,简化配置
 
首先配置一个事务管理器,这里要给datasource,即数据源  
因为是针对mybatis的,所以采用DatasourceTransactionManager实现类
 
第二步配置Advice
 
 
 
最后配置切面,即在程序中有哪些地方需要配置事务
 
 
 这里只总结了spring中的IoC、AOP、事务这三块内容
其他的内容会在mybatis、springmvc里来总结
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

猜你喜欢

转载自www.cnblogs.com/zaj666/p/8990089.html