title: Spring核心知识点
date: 2020-07-09 19:33:10
tags: Spring
categories:
- [Spring框架,Spring]
Spring概述
什么是Spring
Spring是一个轻量级的Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题,简化Java开发,其设计理念是通过IoC容器实现对象耦合关系的管理,从而实现解耦。
Spring的优缺点
优点:
- 方便解耦,简化开发(将所有对象的创建和依赖关系的维护交给Spring管理)
- AOP编程的支持(方便实现对程序进行权限拦截、运行监控等功能)
- 声明式事务的支持(只要通过配置就可以完成对事务的管理,无需手动编程)
- 方便程序的测试(Junit4支持,通过注解方便测试Spring程序)
- 方便集成各种优秀框架(MyBatis、Redis等)
- 降低JavaEE API的使用难度
缺点:
- Spring依赖反射,影响性能
- 使用门槛较高
- 配置较复杂
Spring由哪些模块组成
- Core:提供了框架的基本组成部分,包括控制反转和依赖注入功能;
- Beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean;
- Context:Spring上下文,向Spring框架提供上下文信息,其他程序也可以通过Context访问Spring的Bean资源。
- JDBC:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC
- AOP:提供面向切面的编程实现,可以自定义拦截器、切点等;
- Web:Web开发相关的组件
- Test:为测试提供支持
Spring框架中的设计模式
- 工厂模式:BeanFactory创建对象的实例
- 单例模式:Bean默认是单例模式
- 代理模式:AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
- 模板方法模式:用来解决代码重复的问题,如:JpaTemplate,RestTemplate
- 观察者模式:当一个对象的状态发生改变时,所有依赖于它的对象都能得到通知,如监听器功能。
Spring控制反转(IoC)
什么是Spring IoC容器
控制反转是将传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理,所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,实现了解耦。
Spring IoC负责创建对象,并管理这些对象的整个生命周期。
Spring IoC的实现机制
工厂模式+反射机制
BeanFactory和ApplicationContext的区别
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。
区别:
依赖关系:
-
BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
-
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
- 继承MessageSource,因此支持国际化(i18n)。
- 统一的资源文件访问方式。
- 提供在监听器中注册bean的事件。
- 同时加载多个配置文件。
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
加载方式:
- BeanFactory采用的是延迟加载的形式来注入Bean
- ApplicationContext在容器启动时一次性创建所有的Bean。这样在容器启动时,就可以发现Spring中存在的配置错误,但是不足之处在于占用内存空间,从而导致程序启动较慢。
创建方式:
- BeanFactory通常以编程的方式被创建
- ApplicationContext还能以声明的方式创建,如使用ContextLoader。
ApplicationContext通常的实现是什么?
- FileSystemXmlApplicationContext:通过程序在初始化的时候,导入Bean配置文件,然后得到Bean实例。
- ClassPathXmlApplicationContext:从类路径下的xml文件中加载bean的配置文件;
- XmlWebApplicationContext:在B/S系统中,通常在web.xml初始化bean的配置文件,然后由WebAppliCationContextUtil得到ApplicationContext
什么是依赖注入(DI)
依赖注入(Dependency Injection,DI),是组件之间依赖关系由容器在运行期决定,即由容器动态地将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
依赖注入是实现控制反转的方法和手段。
依赖注入的常见实现方式
-
setter注入
-
构造方法注入
-
注解注入
@Controller public class UserController { // 使用注解自动注入 @Autowired() private UserService userService; // do something } // 创建依赖对象 @Service public class UserService { // do something }
Spring Beans
Bean的5种作用域
- singleton:bean在每个Spring ioc 容器中只有一个实例。
- prototype:一个bean的定义可以有多个实例。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring
ApplicationContext情形下有效。 - session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
对于有状态的bean使用prototype,对于无状态的bean则使用singleton。
有状态就是有数据存储功能,有状态对象就是有实例变量的对象,可以保存数据,是非线程安全的,所以要使用prototype保证安全;
无状态就是一次操作,不能保存数据,无状态对象就是没有实例变量的对象,线程安全,因此使用singleton性能更好。
Spring配置bean实例化有哪些方式?
1)使用类构造器实例化(默认无参数)
<bean id="bean1" class="cn.itcast.spring.b_instance.Bean1"></bean>
2)使用静态工厂方法实例化(简单工厂模式)
//下面这段配置的含义:调用Bean2Factory的getBean2方法得到bean2
<bean id="bean2"
class="cn.itcast.spring.b_instance.Bean2Factory" factory-method="getBean2">
</bean>
3)使用实例工厂方法实例化(工厂方法模式)
//先创建工厂实例bean3Facory,再通过工厂实例创建目标bean实例
<bean id="bean3Factory" class="cn.itcast.spring.b_instance.Bean3Factory">
</bean>
<bean id="bean3" factory-bean="bean3Factory" factory-method="getBean3">
</bean>
Spring配置bean实例化有哪些方式
1)使用类构造器实例化(默认无参数)
<bean id="bean1" class="cn.itcast.spring.b_instance.Bean1"></bean>
2)使用静态工厂方法实例化(简单工厂模式)
下面这段配置的含义:调用Bean2Factory的getBean2方法得到bean2
<bean id="bean2"
class="cn.itcast.spring.b_instance.Bean2Factory" factory-method="getBean2">
</bean>
3)使用实例工厂方法实例化(工厂方法模式)
先创建工厂实例bean3Facory,再通过工厂实例创建目标bean实例
<bean id="bean3Factory" class="cn.itcast.spring.b_instance.Bean3Factory">
</bean>
<bean id="bean3" factory-bean="bean3Factory" factory-method="getBean3">
</bean>
Spring如何解决线程并发问题
在Spring中,绝大部分Bean都可以声明为singleton作用域(不是线程安全的),因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal会为每个线程提供一个独立的变量副本,从而隔离多个线程对数据的访问冲突,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
Bean的生命周期
- 实例化:Spring对Bean进行实例化;
- 属性赋值:Spring将值和Bean的引用注入到Bean对应的属性中;
- 初始化:
- 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值;
- 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);
- 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
- 如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;
- 销毁:当容器关闭时,调用Bean的销毁方法(A:使用配置文件指定的destroy-method属性;B:实现org.springframwork.bean.factory.DisposeableBean接口)
注意:Spring 容器可以管理 singleton 作用域下 bean 的生命周期,在此作用域下,Spring 能够精确地知道bean何时被创建,何时初始化完成,以及何时被销毁。而对于 prototype 作用域的bean,Spring只负责创建,当容器创建了 bean 的实例后,bean 的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期。
Bean生命周期方法
bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。相应的注解是@PostConstruct和@PreDestroy。
Bean注入属性有几种方式?
接口注入、构造器注入、set注入。
@Component 和 @Bean 有什么区别?
它们的作用对象不同:@Component 作用于类,而 @Bean 注解作用于方法。
@Component 通常是通过类路径扫描来自动侦测和装配对象到 Spring 容器中,比如 @ComponentScan 注解就是定义扫描路径中的类装配到 Spring 的 Bean 容器中;@Bean 注解是告诉 Spring 这是某个类的实例,当我需要用它时把它给我,@Bean 注解比 @Component 注解自定义性更强,很多地方我们只能通过 @Bean 注解来注册 Bean,比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean 来实现。
使用@Autowired注解自动装配的过程是怎样的?
使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。
在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
- 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
- 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法是使用required=false。
Spring注解
@Required 注解有什么作用
这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。示例:
public class Employee {
private String name;
@Required
public void setName(String name){
this.name=name;
}
public string getName(){
return name;
}
}
@Autowired和@Resource之间的区别
@Autowired可用于:构造函数、成员变量、Setter方法
@Autowired和@Resource之间的区别
- @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
- @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
@RequestMapping 注解有什么用?
用来标识 http 请求地址与 Controller 类的方法之间的映射,可以注释到类上,也可以注释到方法上。
Spring数据访问
Spring DAO有什么用
Spring DAO(数据访问对象)使得JDB、Hibernate或JDO这样的数据访问技术更容易以一种统一的方式工作,使得用户容易在持久层技术之间切换,而无需考虑捕获每种技术不同的异常。
JdbcTemplate是什么
JdbcTemplate类提供了很多便利的方法解决诸如把数据库数据编程基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。
Spring的事务管理
事务就是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。这样可以防止出现脏数据,防止数据库数据出现问题。
Spring有自己的事务管理机制,一般是使用TransactionManager进行管理,可以通过Spring的注入来完成此功能,Spring提供了几个有关事务处理的类:
- TransactionDefinition:事务属性定义;
- TransactionStatus:代表了当前事务,可以提交、回滚;
- PlatformTransactionManager:Spring提供用于管理事务的基础接口,其下有一个实现的抽象类AbstractPlatformTransactionManager,我们使用的事务管理类如:DataSourceTransactionManager等都是这个类的子类。
一般事务定义步骤:
TransactionDefinition td = newTransactionDefinition();
TransactionStatus ts = transactionManager.getTransaction(td);
try{
//do sth
transactionManager.commit(ts);
}catch(Exception e){
transactionManager.rollback(ts);
}
Spring支持的事务管理类型及其实现方式
Spring支持两种类型的事务管理:
- 编程式事务管理:通过编程的方式管理事务,灵活但难以维护;
- 声明式事务管理:将业务代码和事务管理分离,只需用注解或xml配置文件来管理事务,非侵入性。
声明式事务管理建立在AOP上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,执行完目标方法之后根据执行的情况进行提交或回滚。
事务隔离级别
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,可以读到未提交的内容;
- READ-COMMITTED(读取已提交): 通过“快照读”的机制,保证只能读到已经提交的内容;
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,因为事务开启后,不允许进行“修改,删除”操作;
- SERIALIZABLE(可串行化):
最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,但是效率太差,性能开销也大,几乎不使用。
事务的传播属性
PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED–如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED
Spring AOP
什么是AOP
AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
AOP的名词解释
- 通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。
通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。 - 连接点(JoinPoint):Spring允许你使用通知的地方,一般是方法前后;
- 切入点(Pointcut):连接点是可以使用通知的地方,而切入点是你想要使用通知的地方;
- 切面(Aspect):通知和切入点的结合,通知说明了干什么和什么时候干(时间是通过方法名中的before、after、around等确定的),切入点说明了在哪干(指定使用通知的方法);
- 目标(target):被通知的对象,也就是真正的业务逻辑,被通知的对象可以在毫不知情的情况下,被织入切面,而自己专注于业务本身的逻辑;
- 织入(weaving):把切面应用到目标对象来创建新的代理对象的过程;
Spring AOP和AspectJ AOP有什么区别
AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理,静态代理的代表是AspectJ,动态代理则有Spring AOP和CGLIB。
区别:
- AspectJ是静态代理的增强,AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,它会在编译阶段将Aspect(切面)织入到Java字节码中,运行的时候就是增强后的AOP对象。
- Spring AOP使用的动态代理,AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
JDK动态代理和CGLIB动态代理的区别
- JDK动态代理只提供接口的代理,不支持类的代理,核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
- 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)是一个代码生成的类库,可以在运行时动态地生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
Spring通知类型
通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中,代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean,当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
Spring切面有5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,不关心方法的输出结果;
- 返回通知(After-returning):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。