spring几乎所有技术都基于springframework
一、spring的两大核心:IOC和DI
1.IOC控制反转
反转了资源的获取方向,以前要人为主动的new一个个对象,现在拥有了统一的IOC容器,进行管理,在需要时,只要提供接收资源对象的方式即可,不再需要创建对象了,也不再需要知道容器如何创建对象。
2.DI依赖注入
即Bean以一些提前设定好的方式,比如setter注入,构造器注入,接收来自于容器的资源,是对IOC的具体实现。
2.1 setter注入
在bean标签下,定义子标签<property>
其中配置name为属性的名称,属性名称默认是setXxx方法去掉set后首字母小写。例如set方法setUid,对应的属性为uid,配置value为属性的值,都是字面量
2.2 构造器注入
通过有参构造为bean对象赋值;即在bean标签中定义子标签<constructor-arg>,配置name为参数名称(不是属性名),value配置值。
注意:根据你设置的constructor的个数,他会自动匹配对应参数的构造方法,但若是含有多个相同个数参数的构造方法,那具体要通过哪个来赋值呢?
解决方法是:在constructor标签中,定义name参数类型表明要赋值的属性
2.3 特殊值注入
2.3.1为字面量赋值,字面量就是没有引申意义,比如int a = 10; 10就是字面量,a是变量,代指10。但若是a加上单引号或双引号,那么也是字面量
字面量赋值都是通过value属性来赋值的
为非字面量赋值,例如赋值为null,需要再嵌套一层<null>标签,否则按照字面量赋值方法,赋值的结果只是字符串而已
2.3.2当注入了一些特殊的字符,比如尖括号,因为尖括号是标签的结束和开始标记,需要进行转义,这里可以使用<;和>;代替小于号和大于号
或者使用CDATA区,该区域内所有的字符都会原样解析,注意写的时候,不要写在属性的未知数,用一个value标签包裹起来,在idea中输入CD就出来了
2.3.3 为类类型的属性赋值
有三种方式:
引入外部bean:在property中定义ref属性指定引用的外部bean的id
使用内部bean:在property标签中再定义一个bean标签,里面定义类类型的属性
既然是内部bean,则作用域只存在包裹它的property中,也就是说ioc容器获取不到该内部bean
级联:通过对象名.属性名赋值,但是使用级联,必须先为对应的类类型进行实例化,其实级联就是对bean进行重新赋值
2.3.4 为数组类型赋值
在property中定义子标签<array>
2.3.5 为list集合类型赋值
和数组一样,使用内部bean方式,定义一个<list>子标签,里面若是字面量类型就用value,类类型就用ref
如果外部有一个list类型的bean,也可以直接通过ref引入外部bean,需要引入一个新的util约束
2.3.6 为Map集合类型赋值
通过子标签<map>实现,里面嵌套子标签<entry>定义key和value,类类型使用key-ref和value-ref
使用util:map设置一个map集合类型,用法和util:list一样,在bean定义使用时用ref引用这个util的id就可以了
2.3.7 使用p命名空间赋值(前提引入约束)
同样,类类型需要ref来赋值
2.4 spring管理数据源和引入外部属性文件
2.4.1数据源本质上是一个对象,因此也可以上交给ioc容器管理
2.4.2 引入外部properties文件
然后在xml中通过<context:place-holder>来引入,引入后就可以使用${}的形式来替换value中写死的数据了
二、IOC容器在spring中的实现
1.BeanFactory,spring内部使用的接口,开发时不使用
2.ApplicationContext,BeanFactory的子接口,面向我们,一般常用这个接口
3.ConfigurableApplicationContext,是ApplicationContext的子接口,具有关闭,刷新容器的功能
4.实现类
ClassPathXmlApplicationContext,通过类路径获取xml文件(常用)
FileSystemXmlApplicationContext,通过文件系统,读取磁盘上的xml文件
三、IOC容器创建对象的方式(反射+工厂模式)
在resources目录下生成applicationContext.xml文件,在里面定义bean标签完成配置
1).获取bean的三种方式
通过类型获取bean,如果ioc容器中存在多个相同类型的bean,则获取时会报错
如果bean对应的类实现了接口,则通过接口类型也可以获取到bean,但前提是bean唯一
2). Bean的作用域
通过指定bean标签中的scope属性来定义作用域。
singleton:默认,单例,通过单例获取的对象都是同一类型的对象
prototype:在ioc容器中可以存在多例同类型的bean
不同的作用域,对bean的生命周期有不同影响。
单例情况下,bean在ioc容器加载时就会自动创建,使用时自动返回当前bean即可。
多例情况下,只有在自己主动获取bean对象时,ioc容器才会创建对应类型的bean,而且bean的销毁也不随ioc容器关闭而销毁了
2.1 Bean的生命周期
因为bean对象不是我们创建,而是ioc容器自己创建,所以我们不需要知道实现过程,但需要指定对应的生命周期。
2.1.1. 实例化:通过无参构造器创建bean对象
2.2.2 依赖注入:给bean对象进行赋值操作 (setXxx方法注入或构造器注入)
2.3.3. 初始化前的方法:postProcessBeforeInitialization()
2.4.4. 初始化:在bean标签定义init-method为对应的方法引用
2.5.5. 初始化后的方法:postProcessAfterInitialization()
2.6.6. 销毁时的方法:在bean标签定义destory-method为对应的方法引用,在ioc容器关闭时,bean才会销毁。
2.2 Bean的后置处理器,可以在bean的初始化之前或之后添加额外的操作
创建方式:设置实现类实现BeanPostProcessor接口
重写方法:
postProcessBeforeInitialization(),返回bean,并加入对应逻辑
postProcessAfterInitialization(),返回bean,并加入对应逻辑
然后配置到ioc容器中,也就是在xml中定义bean标签,否则ioc无法识别。
3).FactoryBean
是一个接口,需要创建实现类实现该接口,然后重写里面的两个方法
getObject():将一个bean对象交给ioc容器管理
getObjectType():设置bean工厂提供bean的类型
isSingleton():是否为单例
然后把factoryBean的实现类在xml中配置为一个bean时,它就会把getObject中返回的对象作为一个bean交给ioc容器管理。
四、基于xml方式的bean自动装配
上述配置bean方式都是在xml中手动装配,定义bean嵌套property,ref子标签。
在mvc的三层架构中,controller依赖于service,service依赖于dao,所以在基于xml手动装配中,要在对应依赖的类中添加get,set方法完成依赖注入,然后在xml文件里配置mvc的三层bean结构,bean里添加property完成依赖关系
而自动装配是按照指定的策略,在ioc容器中匹配到某个bean,自动为类类型或接口类型的bean赋值。
在xml方式,可以在bean标签的属性定义autowire属性,来代替property的手动装配,autowire的属性有多种:
byType:按类型自动装配
byName:按根据属性的属性名作为bean的id来进行自动装配
no:不装配
dafault:默认值,和no一样,不自动装配
若在ioc容器中,根据类型匹配bean,一个bean都匹配不到,此时不再匹配,使用默认值null进行赋值,这时就容易发生空指针异常,而且同时有且只有一个相同类型的bean
五、基于注解的方式管理bean
1.基于注解实现ioc
在MVC的三层架构中,使用注解@Controller,@Service,@Repository,@Component
来分别将类交给ioc容器。就不用再在xml里定义bean标签了
2.配置完注解,需要让ioc容器扫描到,所以要在xml中定义组件扫描
base-package定义了扫描的包名
子标签exclude-filter定义了不扫描哪些包,type属性有annotation通过注解扫描和assignable通过类扫描, expression是注解或类的全类名
子标签include-filter定义了只扫描哪些包,使用前需要先设置属性user-default-filters为false,表示不扫描所有包,这样才能只扫描某些包
通过注解扫描配置的bean,默认id为属性的首字母小写,也可以通过注解里的value属性自定义bean的id
3.基于注解完成自动装配
使用@Autowired代替xml中的autowired属性,并且不用再添加get,set方法完成注入
@Autowired可以放在成员变量上或set方法上。
@Autowired默认是按类型匹配也就是byType,如果ioc容器中有多个同类型的bean,则会自动
转换为按名称byName的方式进行装配,如果这两种方式都无法实现自动装配
可以再添加一个@Qualifer注解,通过value属性指定要装配的bean的id,来进行装配
使用注解自动装配,时要求必须完成装配,如果无法装配成功,就会报错,可以设置required属性为false,表示不强制进行自动装配,即装配不上时,使用默认值。
六、AOP
AOP是面向切面编程,指的是在不修改源代码的情况下,对程序进行功能增强的一种技术
1. AOP的相关术语
横切关注点:从目标对象抽取出来的非核心业务代码。可以存在多个
通知:每一个横切关注点上,都需要对应的通知方法来实现,就叫做通知方法
1)前置通知@Before:在代理的目标方法执行前执行
2)返回通知@AfterReturning:在代理的目标方法成功结束后执行
3)异常通知@AfterThrowing:在代理的目标方法异常结束后执行
4)后置通知@After:在代理的目标方法最终finally结束后执行
5)环绕通知@Around:使用try catch finally包裹的方法,其实是上述四种方法的集合
切面:封装通知方法的类Aspect
目标:被代理的目标对象
代理:向目标对象应用代理后生成的代理对象,自动创建
连接点:逻辑概念,就是抽取横切关注点的位置
切入点:物理概念,定位连接点,然后套入增强的方法
2. 基于注解的AOP实现
1)通过AspectJ和代理方式实现,在pom文件引入对应依赖spring-aspects
2) 定义切面类Aspect,并使用@Component、@Aspect注解交给ioc容器管理,并声明为切面类,并且在配置文件定义组件扫描<context:component-scan>,定义<aop:aspectj-autoproxy>表示开启基于注解的aop
3)在切面类定义通知方法,每一种通知方法对应了不同的注解,在注解中,必须指定切入点表达式,表明套入源代码的地方,即横切关注点,切入点表达式为
execution():里面指定访问修饰符,返回值类型,包,方法,参数,其中前两个一般都会省略。
该图是使用了切入点表达式的重用,通过@Pointcut定义一个方法,定义一个公有的切入点表达式
之后所有的通知方法都可以使用该表达式
4)定义了aop,就不能直接访问目标对象了,必须通过代理对象执行目标对象的方法,因为是动态代理,不知道具体代理对象的类型,但知道,代理对象一定实现了和目标对象一样的接口或父类,所以获取父类接口,然后再执行方法即可。
5)在通知方法中获取连接点的信息
可以在通知方法的参数列表中,定义形参JoinPoint jp
该类型的参数,包含了连接点的信息,通过对应方法就可以获得连接点的方法签名,参数等等
注意,在环绕通知中,只能定义ProceedingJoinPoint对象,功能和JoinPoint一样,但多了一个proceed()方法,表示调用目标对象方法
6)切面的优先级,因为我们可能会创建多个切面,多个切面间,通知方法的执行顺序就不知道了,这样可以使用@Order注解,里面的value属性定义一个int类型的数字,数字越小,默认优先级越高,也就会优先执行该切面类的通知方法
7)基于xml的aop实现
如图配置即可,首先关闭基于注解的aop标签,然后定义<aop:config>
开启手动配置aop,定义子标签<aop:aspect>表明切面类 ref是切面类的全限定类名
<aop:aspect>中嵌套子标签来定义通知方法或者重用的切入点表达式
七、spring整合junit完成声明式事务
在pom文件引入依赖spring-test,这样在test中,就不用再主动获取ioc容器了,转而通过注解
@RunWith表示使用junit4的测试环境
@ContextConfiguration表示读取的xml配置文件
以往的事务都是编程式事务,也就是事务相关的操作全部由自己完成,代码重用性不高
通过声明式事务,可以把相同逻辑的代码抽取到切面并封装1,我们只需要在配置文件简单配置即可操作,提高了效率,消除了冗余的代码
1. 基于注解的声明式事务
pom中引入对应依赖,在配置文件中配置数据源连接到数据库
在配置文件中配置事务管理器
开启事务的注解驱动
这样就能在业务类中或对应的方法上,定义注解@Transactional,表示开启为当前整个类或方法开启事务
在注解@Transactional中包含了多个属性
1)只读readonly,这个属性只涉及读操作,对增删改设置只读会爆出sql异常
2)超时timeout,超过一定时间未执行成功的操作,会自动放弃并报超时异常
3)回滚rollback,遇到什么异常时会进行回滚,有四个属性
rollbackFor:因为什么异常的class对象来回滚
rollbackForClassName:因为一个字符串的异常类型的全限定类型名来回滚
norollbackFor:不因为什么异常的class对象来回滚
norollbackForClassName:不因为一个异常类型的全限定类型名来回滚
声明式事务默认情况是对所有的运行时异常都进行回滚,所以很少设置rollbackFor和rollbackForClassName
4)事务的隔离级别
5)事务的传播行为
定义属性propagation。
当a事务中调用b时,因为a,b都有各自的事务,那么b究竟按照谁的事务,是a的,还是b本身的事务进行操作?
Propagation.REQUIRED是默认值,意思是使用调用者a的事务
Propagation.REQUIRES_NEW意思是调用事务b时,不使用事务a,反而开启一个新的事务,也就是使用事务b
2. 基于xml的声明式事务
配置事务通知