spring
2020-12-24 18:43:26 6 举报
AI智能生成
spring思维导图
作者其他创作
大纲/内容
IOC
依赖注入
装配方式(依赖注入的具体行为)
实现方式 基于注解的装配方式
注解
@Autowire
1、优先按照byType方式查找
2、如果查找的结果不止一个,而且没有设置名称的话,那么会报错
3、否则@Autowire会按照byName方式来查找
4、如果查找为空,那么会抛出异常,可使用required=false来解决
@Resource
1、默认按照byNmme进行装配,名称可以通过name属性指定
2、如果没有指定name属性,当注解写在字段上时,默认取字段名按照名称查找,如果写在setter方法上,默认取属性名进行装配
3、当通过byName找不到时,通过byType查找,但是要注意,如果那么属性一旦指定,就只会按byName方式装配
4、只指定@Resource注解的Type属性,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,就会抛出异常
自动扫描(component-scan)
标签将会开启spring beans的自动扫描,并可设置base-package属性,表示spring将会扫描该目录及其子目录下所有被@Component标注的类,对他们进行自动装配
基于xml配置的显示装配
能够在编译时就发现错误 基于java配置的显示装配
依赖注入的方式
构造器方式注入
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>例子
<constructor-arg ref="beanTwo"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>例子
构造器依赖注入,通过容器触发一个类的构造器来实现,该类有一系列参数,每个参数代表一个对其它类的依赖
使用构造器注入的好处
1、保证依赖不可变(final关键字)
2、保证依赖不为空
实例化Controller的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要spring容器传入所需要的参数,这样保证了依赖不为空
3、避免循环依赖
在构造器注入传入参数时,比如在A中注入B,在B中注入A,先初始化A,那么需要传入B,这时候发现B没有初始化,那么就要初始化B,这个时候就出现了问题,会排出循环依赖错误,而使用field注入的方式,则只能在运行时才会遇到这个问题。
setter方法注入
例子:<bean id="exampleBean" class="examples.ExampleBean">
<property name="beanTwo" ref="yetAnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
<property name="beanTwo" ref="yetAnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
之所以叫setter方法注入,因为是通过找到类的对应的setter方法,再进行相应的注入
setter方法注入是容器通过调用无参构造器,或者无参static工厂方法实例化bean后,调用该bean的setter方法,既完成了基于setter的依赖注入
最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖
循环依赖
构造器方式无法解决,只能抛出异常
因为加入singletonFactories三级缓存的前提是执行了构造器(因为需要先构造出对象),所以构造器循环依赖无法解决
多例方式无法解决,只能抛出异常
因为spring容器不缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean
单例模式可以解决
通过三级缓存解决
在createBeanInstance()之后会调用addSingleton()方法将bean注册到singletonFactories中
举例
1、A中的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象
2、A首先完成了初始化的第一步,并且将自己提前暴露到singletonFactories中(这步是关键)
3、A发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,这时就走create流程
4、B在初始化第一步的时候发现了自己依赖对象A,于是尝试get(A),尝试在一级缓存singletonObjects中获取(肯定没有,因为A还没初始化完全),然后尝试在二级缓存earlySingletonObjects中获取(也没有),最后在三级缓存singletonFactories中获取,由于A通过ObjectFactory将自己提前曝光,所以B能够通过ObjectFactory.getObject拿到A对象
5、B拿到A之后顺利完成步骤1,2,3,完全初始化之后将自己放到一级缓存singletonObjects中
6、返回A中,A此时能拿到B的对象,顺利完成2,3,最终A也完成了初始化,将自己放到一级缓存singletonObjects中
7、由于B拿到了A对象的引用,所以B类中的A对象也完成了初始化
不用创建对象,而只需要描述他如何被创建,不在代码里组装组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC)负责把他们组装起来。
容器的初始化过程
1、Resource定位;指定beanDefinition的资源定位过程。通俗的讲,就是找到定义JavaBean信息的xml文件,并将其封装成Resource对象
2、BeanDefinition的载入;把用户定义好的javabean表示为IOC容器的内部数据结构,这个容器内部的数据结构就是BeanDefinition
3、像IOC容器注册这些BeanDefinition
bean知识
bean的生命周期
1、实例化bean
2、设置bean的属性
3、检查Aware相关接口并设置相关依赖
BeanNameAware
spring将bean的id传给setBeanName()方法
BeanFactoryAware
会调用它实现的setBeanFactory(BeanFactory)传递的是Spring工厂本身(可以用这个方式获取其它bean,只需要在spring配置文件中配置一个普通的bean就可以)
ApplicationContextAware
会调用setApplicationContext(ApplicationContext)方法,传入spring上下文
4、检查BeanPostProcessor接口并进行前置处理
调用postProcessBeforeInitialization(Object obj,String str)方法
5、检查bean在spring配置文件中配置的init-method属性,并自动调用其配置的初始化方法
由于这个方法是bean初始化后调用的方法,所以可以应用于内存或者缓存技术;
通过提前暴露一个单例工厂方法,从而使其它bean能够引用到该bean/提前暴露一个正在创建中的bean
6、检查BeanPostProcessor接口并进行后置处理
调用postProcessAfterInitialization(Object obj,String str)方法
7、当bean不需要的时候,会经历清理阶段,如果bean实现了DisposableBean这个接口,会调用destory()方法
8、最后,如果bean的spring配置文件中配置了destory-method方法,会自动调用其销毁方法
bean的作用域
singleton
1、默认的作用域
2、默认情况下是容器初始化的时候创建,但也可以设定运行时在初始化bean
3、DefaulltSingletonBeanRegistry中的singletonObjects哈希表保存了单例对象
spring容器可以管理singleton作用域下bean的生命周期,在此作用域下,spring能够精确的知道bean什么时候被创建,什么时候初始化,以及什么时候被销毁
prototype
每次通过spring容器获得prototype定义的bean时,容器都将创建一个新的bean实例,每个bean实例都有自己的属性和状态
当容器创建了bean实例后,bean的实例就交给了客户端的代码管理,spring容器将不再跟踪其生命周期,并且不会再管理其生命周期
对有状态的bean使用prototype作用域,对没有状态的bean使用singleton作用域
request
session
application
websocket
bean的创建过程
1、进入getBean()方法
2、判断当前bean作用域是不是单例,如果是则去对应缓存中查找,没找到的话则新建实例并保存,如果不是单例则直接新建实例(createBeanInstance)
3、新建实例后注入属性,并处理回调
创建bean
找到@Autowired的对象
创建注入对象,并赋值
大致流程
1、首先根据配置文件读取对应的包,读取包中的类。找到所有含有@bean,@service注解的类,利用反射解析他们,包括解析构造器,方法,属性的等,然后封装成各类信息放到container中(其实就是一个map里)--》IOC容器初始化
2、获取类的时候,首先从container中查找是否有这个类,如果没有则报错,如果有则通过构造函数new出来
3、如果这个类含有其他需要注入的属性,则进行依赖注入。如果有则还是从container中获取对应的解析类,然后new出对象,并通过之前解析出来的信息类找到setter方法(setter方法注入),然后用该方法注入依赖对象(这就是依赖注入),如果其中有一个类contanier中没有找到,则抛出异常
4、如果有嵌套bean则通过递归解析
5、如果bean的作用域是singleton,则会重用这个bean不创建新的,将这个bean放在map里,每次都先从这个map里找。如果作用域是session,则会放在session里。
总结:通过解析xml配置文件,获取bean的属性(id、name、class、scope等)里面的内容,利用反射原理创建bean对象,存入spring的bean容器中。
springMVC
执行流程
1、用户发送请求至前端控制器 DispatcherServlet
2、DispatcherServlet收到请求调用HandlerMapper处理器映射器
3、处理器映射器根据URL找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet
4、DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5、执行处理器(Controller,也叫后端控制器)
6、Controller执行完返回ModelAndView
7、HandlerAdapter将controller执行结果ModelAndView返给DipatcherServlet
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、ViewReslover解析后返回具体的View
10、DispatcherServlet对View进行渲染视图(即将模型数据填充到视图中)
11、DispatcherServlet响应用户
常用注解
@Component
@Controller
@Service
@Repository
在类上增加@Component注解,他会被spring容器识别,并转换为bean
@RequestMapping
@RequestParam
@Autowired
@Resource
servlet生命周期
1、加载和实例化
2、初始化
3、请求处理
4、服务终止
spring类
ApplicationContext
AnnotationConfigApplicationContext
ClassPathXmlApplicationContext
基本概念
spring优点
轻量级,非侵入式
何为非侵入式,就是对现有类结构没有影响
可提供众多服务
例如事务管理、WS等
很好的支持AOP,方便面向切面编程,使得业务逻辑和系统服务分开
对主流框架提供很好的支持
例如:hibernate,Struts2,JPA等,
通过IOC容器,将对象之间的依赖关系交给spring管理,降低组件之间的耦合,让我们更专注于应用逻辑
spring的DI机制降低了业务对象替代的复杂性
spring的高度可开放性,并不强制依赖于spring,开发者可以自由选择spring全部或者部分模块
spring缺点
缺少一个公共控制器
没有springboot好用
spring很好的整合了主流框架,所以如果涉及到拆分,恐怕不容易拆分
AOP
实现原理
JDK动态代理
主要通过Proxy.newProxyInstance()和InvocationHandle这两个类和方法实现
实现过程
1、创建代理类proxy实现接口InvocationHandler ,重写invoke()方法
调用被代理类的方法时,默认调用此方法
2、将被代理类作为构造函数的参数传入代理类proxy
3、调用Proxy.newProxyInstance(ClassLoader,Interfaces, handler)方法生成代理类;
生成代理类
$Proxy0 extend Proxy Implenments Penson
类型为$Proxy0
因为$Proxy0 已经继承了Proxy,所以java动态代理只能对接口进行代理
代理对象会实现用户提供的这组接口,因此可以将这个代理类强制转换成这组对接口的任意一个
通过反射生成对象
总结:代理类执行自己的方法时,通过自身持有的中介类对象来调用中介类的invoke方法,从而达到代理执行被代理对象的方法
CGLIB
1、生成对象类型为Enhancer
2、实现原理类似jdk动态代理,只是他在运行期间生成的代理对象是目标类的子类
3、spring在运行期间通过CGLIB继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程
静态代理
缺点
1、如果代理一个接口的多个实现的话需要定义不同的代理类
2、代理类和被代理类必须实现相同的接口,万一接口有变化,代理类和被代理类都需要修改,难以维护
在编译的时候就生成了代理类
JDK动态代理和CGLIB动态代理对比
1、CGLIB创建的动态代理对象实际运行中要比jdk动态代理性能高
1.6、1.7的时候CGLIB快
1.8,,,JDK快
2、CGLIB创建动态代理对象的时候要比jdk慢
3、singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以采用CGLIB动态代理,反之,则适用JDK动态代理
4、JDK动态代理是面向接口的,而CGLIB动态代理是通过字节码底层继承代理类来实现的(所以,如果代理类被final修饰,则会失败)
5、JDK生成的代理类类型是Proxy(因为继承的是Proxy),CGLIB生成的代理类类型是Enhancer类型
6、如果被代理类是个实现类,那么spring将会采用jdk产生代理(spring默认采用jdk动态代理机制)
如果被代理类不是个实现类,那么spring则采用CGLIB产生代理
如果被代理类不是个实现类,那么spring则采用CGLIB产生代理
配置方式
XML
注解
基于java配置类
通过@Configuration和@Bean这两个注解实现
@Configuration注解配置在类上,相当于一个xml配置文件
@Bean注释在方法上,相当于xml配置中的<bean>
基本概念
核心业务功能和切面功能分别独立进行开发,然后把核心业务功能和切面功能“编制”在一起,这就叫做AOP
让关注点代码与业务代码分离
面向切面编程就是指:对很多功能重复的代码进行抽取,再在运行的时候往业务方法上动态植入“切面类代码”
应用场景:日志,权限管理,事务控制
事务管理
基本概念
如果需要某一组操作具有原子性,就用注解的方式开启事务,按照给定的事务规则来执行提交或者回滚操作(事务是一组逻辑操作,要么都成功,要么都失败)
事务的特性(ACID)
原子性(Atomicity)
事务的最小执行单位,不可分割,事务的原子性保证,要么都成功,要么都失败
一致性(Consistency)
执行事务前后,数据保持一致
隔离性(Isolation)
并发访问数据库时,一个用户的事务不被其他事务干扰,各并发事务之间,数据库是独立的
持久性(Durability)
一个事务被提交之后,他对数据库中的数据改变是持久的,即使数据库发生故障,也不会对其数据有影响
并发事务带来的问题
脏读(Dirty read)
当一个事务正在访问数据库并且对数据做了修改,而这种修改还没提交到数据库中,这时另一个事务读取了该数据,然后使用了这个数据,由于这个数据还没有提交,所以另一个事务读取的就是脏数据,依据脏数据所做的操作,可能是错误的。
丢失修改(Lost to modify)
两个事务同时修改了一条数据,导致第一个事务修改的数据丢失,例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
不可重复读(Unrepeatableread)
一个事务多次读取同一条数据,但是这时候第二个事务进来修改了这条数据,导致第一个事务第二次读的时候,读到的结果不一样,这就发生了一个事务两次读到的数据不一致了。
幻读(Phantom read)
幻读与不可重复读类似,事务1读了几行数据,接着事务2插入或者删除几条,在随后的查询中,事务1会发现多了一些原本不存在的数据,这就是幻读,不可重读读是修改,幻读是插入或者删除。
事务的隔离级别:SQL定义了4个隔离级别
READ-UNCOMMITTED(读取未提交)
最低的隔离级别,可能导致脏读,不可重复度,幻读;
READ-COMMITTED(读取已提交)
允许读取并发事务已提交的数据,可以避免脏读,但是幻读和不可重复读仍可能发生。
REPEATABLE-READ(可重复读)
对同一个字段多次读取结果是一样的,除非被事务本身修改,可以阻止脏读,不可重复读,但是幻读依然会发生。
SERIALIZABLE(可串行)
最高的隔离级别,完全服从ACID,所有的事务依次逐个执行,这样事务之间就不可能产生干扰,可以阻止脏读,幻读,和不可重复读。
事务控制
编程式事务控制
用户通过代码的形式手动控制事务
Conn.setAutoCommite(false)//设置手动控制事务
粒度较细,比较灵活,但是开发起来比较繁琐,每次都要开启、提交、回滚
声明式事务控制
spring提供对事务的控制管理
XML方式
注解方式
事务属性
事务的传播行为
required_new
如果当前方法有事务了,当前方法事务会挂起,在为加入的方法开启一个新事务,直到新事物执行完毕,当前方法的事务才开始
required(默认方法)
如果当前方法已经有事务了,加入当前方法事务
其余五中方式(扩展学习)
事务的传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时候事务如何传播
数据库隔离级别
TransactionDefinition.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别。MySql默认采用REPEATABLE_READ隔离级别,oracle默认采用READ_COMMITTED隔离级别
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读,幻读,不可重读
TransactionDefinition.ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读和不可重复度仍然有可能发生
TransactionDefinition.ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被自己本身事务所修改,可以阻止脏读和不可重复度,但是幻读仍然有可能发生
TransactionDefinition.ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID隔离级别,所有的事务依次逐个执行,这样事务之间完全不可能产生影响,也就是说,该级别完全可以防止脏读,幻读,和不可重复读,但是这将非常影响程序性能,一般情况下也不采用该隔离级别
事务超时属性
指一个事务允许执行的最长时间,如果超过该时长,事务还没有结束,则自动回滚事务
事务只读属性
对事务资源是否执行只读操作
回滚规则
定义了哪些异常会导致事务回滚,哪些不会。默认情况下事务只有遇到运行时异常才会回滚,而遇到检查时异常时不会回滚,也可以用户自己定义
spring事务管理接口
PlatformTransactionManager
(平台)事务管理器
spring并不直接管理事务,而是提供了多种事务管理器,通过PlatformTransactionManager这个接口,spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但具体的实现都是各自平台的事情。
TransactionDefinition
事务定义属性(事务的传播行为、隔离级别、超时。只读以及回滚规则)
TransactionStatus
事务运行状态
事务管理一般在Service层
如果在DAO层,回滚的时候,只能回滚到当前方法,但我们在Service层的方法都是由很多的DAO层方法组成。
如果在Dao层,commit次数会过多
相关设计模式
1、单例模式
实现方式
spring依赖注入bean实例默认是单例的
2、工厂模式
实现方法
静态工厂
beanFactory
工厂模式
factoryBean
3、代理模式
实现方式
AOP中的JDK和CGLIB动态代理
4、适配器模式
实现方式
SpringMVC中的适配器HandlerAdatpter
实现原理
HandlerAdatpter根据不同的Handler规则执行不同的Handler
5、装饰器模式
实现方式
spring中用到的包装器模式在类名上有两种表现:一种类名中包含Wrapper,另一种类名包含Decorator
6、观察者模式
实现方式
spring的事件驱动模型使用的是观察者模式,Spring中Observer模式常用的地方是listener的实现
具体实现
事件机制的实现需要三个部分,事件源,事件,事件监听器
7、策略模式
实现方法
spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,spring本身大量使用了Resource接口来访问底层资源
8、模板方法模式
0 条评论
下一页