欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring知识点小结

程序员文章站 2022-06-13 20:24:02
...

Spring知识点小结

Spring概念

Spring是一个开源的轻量级的IOC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。

Spring的优点

1.低侵入式设计,代码污染极低

2.独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺

3.Spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;

4.Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。

5.Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问

6.Spring并不强制应用完全依赖于Spring,开发者可*选用Spring框架的部分或全部

7.Spring对于主流的应用框架提供了集成支持。

常用Spring模块

Spring Core:核心类库,提供IOC服务;

Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);

Spring AOP:AOP服务;

Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;

Spring ORM:对现有的ORM框架的支持;

Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;

Spring MVC:提供面向Web应用的Model-View-Controller实现。

IOC和DI

IOC:是控制反转。组件向容器发起资源请求,容器查找资源并返回资源。容器会主动将资源提供给它所管理的组件,组件选择一个合适的方式来接收资源,这也是查找的被动式。实际就是你在xml文件控制,控制权的转移是所谓反转,侧重于原理。

DI:是依赖注入,组件以一些预先定义好的方式(如:setter方法)接收来自容器的资源注入。创建对象实例时,为这个对象注入属性值或其它对象实例,侧重于实现。有三种注入方式 :构造器注入、setter方法注入、根据注解注入。

IOC和DI的实质:他们描述的是同一件事情,就是创建对象实例的权限从应用程序控制剥离到容器控制,即容器通过XML等配置控制对象实例来代替使用new关键字实例化一个对象。这样的方式最大的好处就是解耦。

IOC和DI的区别:只是从不同的角度进行描述:IOC是从容器的角度,描述从,之前的由应用程序到容器中获取资源,到,现在的由应用程序请求之后容器进行资源提供,的一个过程。DI是从应用程序的角度,描述由,容器配置对对象实例进行注入,来代替应用程序到容器中获取资源。

什么是Spring Bean

Spring Bean是被实例的,组装的及被Spring容器管理的Java对象。Spring容器会自动完成@bean对象的实例化。创建应用对象之间的协作关系的行为称为:装配(wiring),这就是依赖注入的本质。

Spring Bean的生命周期

Servlet的生命周期:实例化,初始,接收请求,销毁

Spring Bean生命周期也类似与之类似

1.实例化Bean与依赖注入

  • 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean

  • 实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入

2.处理Aware接口

  • 接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean

  • 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值

  • 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身

  • 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文

3.InitializingBean 与 init-method

  • 如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法

4.BeanPostProcessor

  • 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术

5.接收请求

  • 以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了

6.DisposableBean与destroy-method

  • 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法

  • 如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法

Spring中的工厂容器有哪两个

BeanFactory和ApplicationContext

BeanFactory接口是Spring最原始的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。

ApplicationContext接口是BeanFactory的派生,处理BeanFactory包含的功能,还有以下的功能:

  • 继承MessageSource,因此支持国际化。

  • 统一的资源文件访问方式。

  • 提供在监听器中注册bean的事件。

  • 同时加载多个配置文件。

  • 载入多个上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

BeanFactory和ApplicationContext的区别

BeanFactroy采用的是延迟加载形式来注入Bean,即通过new (BeanFactory的实现类)来启动Spring容器时,并不会创建Spring容器里面的对象,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。

这样的流程会出现一些问题。在调试过程中,我们不能立刻发现Spring在配置中存在的问题,假设Bean的某一个属性没有注入,BeanFacotry加载后,直至使用getBean方法调用的时候才会抛出异常,这样很明显不利于后期的维护和异常处理。

通过new(BeanFactory的实现类)来启动Spring容器也就是说BeanFactory通常以编程的方式被创建。支持手动注册BeanPostProcessor、BeanFactoryPostProcessor。

ApplicationContext是在容器启动时,一次性创建了所有的Bean。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,当你需要的时候,直接使用即可。

与BeanFactory相比,这样方式,让我们在容器启动时,就可以发现Spring配置中存在的错误,并及时处理,有利于检查所依赖属性是否注入。

ApplicationContext可以通过实现类创建,也可以通过声明的方式创建。支持自动注册BeanPostProcessor、BeanFactoryPostProcessor。

当然,ApplicationContext和基本的BeanFactory相比,存在占用内存空间不足的问题,当应用程序配置Bean较多时,程序启动较慢。

bean的作用域(scope)

  • singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。

  • prototype:为每一个bean请求提供一个实例。

  • request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

  • session:每个session中有一个bean的实例,在session过期后,bean会随之失效。即同一个Http Session共享一个Bean实例

  • global-session:全局作用域,在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用Portlet context时有效。

bean的装配方式

  • XML方式配置,直接在XML文件中,使用标签配置。
public class User {

}
 <bean id="user" class="User"></bean>
  • 基于注解的方式,在XML文件中设置需要被扫描注解的包,在被使用的类上添加注解,需要使用时,使用@Autowired即可。
@Component
public class Test {
 
    public void testMethod() {
        System.out.println("test Autowired");
    }
}
<context:component-scan base-package="com.****.****"/>
@Autowired
private Test test;
 
@Test
public void test() {
    test.testMethod();
}
  • 使用Java类进行Config配置,使用@Configuration和@Bean来代替XML配置,效果其实是一致的,值得注意的是@Bean注解的都是默认单例模式
@Configuration
public class BeanConfig {
    
    @Bean
    public BeanFactory beanFactory(){
        return new BeanFactoryTest();
    }
}
public class BeanFactoryTest implements BeanFactory {
   
    @Override
    public void Beantest() {
        System.out.println("基于类的Java Config的bean!");
    }
}

bean的注入方式

  • 设值注入,直接在bean标签下,使用property标签一一对应写入。实体类中被注入的属性需要有setter方法
<bean id="user" class="com.***.UserInfo">
    <property name="id" value="1" />
    <property name="name" value="Tom" />
    <property name="age" value="25" />
</bean>
  • 构造器注入,通过含参的构造方法,接收注入的属性值。index是属性的顺序(0就是第一个属性)。
public class TestBean{  
  
    private User user;   
    private String name;     
    private List list;  
      
	//构造器     
    public TestBean(User user,String name,List list){  
        this.user = user;  
        this.name = name;  
        this.list = list;  
    }  
}
<!--构造器方式注入-->  
<bean id="testBean" class="com.***.TestBean">  
    <constructor-arg index="0" type="com.***.UserInfo" ref="user"/>  
    <constructor-arg index="1" type="java.lang.String" value="Tom"/>  
    <constructor-arg index="2" type="java.util.List">  
        <list>  
            <value>list1</value>  
            <value>list2</value>  
            <value>list3</value>  
        </list>  
    </constructor-arg>  
</bean>  
  • 工厂注入,通过一个类(工厂),在工厂中完成初始化,在XML文件中通过工厂中的方法进行注入。
public class UserFactory {
	
	// 静态方法
	public static User getStaticUser() {
		return new User(1,"Tom",25);
	}
    
    // 普通方法
	public User getUser() {
		return new User(1,"Tom",25);
	}
 
}
<!-- 静态方法 -->
<bean id="staticUser" class="UserFactory" factory-method="getStaticUser"></bean>
<!-- 普通方法 -->
<bean id="userFactory" class="UserFactory"></bean>
<bean id="user" factory-bean="userFactory" factory-method="getUser"></bean>

用注解方式将对象注册到Spring容器中

分别是:@Component@Service@Controller@Respository

Spring框架最早出现的只有@Component注解,但如果所有的对象都使用同一个注解,很难区分对象究竟属于哪一层架构。之后又推出了@Service(Service层)、@Controller(Controller层)、@Respository(Dao层)三种注解,用于区分对象属于哪一层架构。4种注解方式从功能上来说没有任何区别,名称的不同只为区分对象的层级。

在类上添加注解后,在XML文件中,设置扫描注解的包

<context:component-scan base-package="com.***.***" />

在Spring框架xml配置的自动装配:

  • no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。

  • byName:通过bean的名称进行自动装配,如果一个bean的property与另一bean的name相同,那么可以不写这个property,就进行自动装配。

  • byType:通过参数的数据类型(class)进行自动装配,寻找与属性类型相同的bean,若一个bean的数据类型,兼容另一个bean中Property的数据类型,则自动装配。

使用byType首先需要保证同一类型的对象,在spring容器中唯一,若不唯一会报不唯一的异常

例如以下这样,通过byType就会报不唯一的错误

  • constructor:利用构造函数进行装配,其实就是根据构造方法的参数类型进行对象查找,相当于采用byType的方式,就是需要有含参的构造方法。

  • autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,如果失败再尝试使用byType。

若该XML文件中,所有的装配方式都是一致的,那么可设定默认装配方式,而不需要在每个bean上设置

<!-- 例如设置为byType -->
<beans default-autowire="byType">

注解实现的自动装配

@Autowired

默认使用byType来装配,如果匹配到类型的多个实例,再通过byName来确定Bean;若找不到,则报错。

Autowired的required的属性,默认为true,表示注入的时候,该bean必须存在,否则就会注入失败;当设置@Autowired(required=false)到时候,表示忽略当前要注入的bean,如果有,直接注入,没有跳过,不会报错。

@Qualifier:

通过byName来确定Bean,必须与@Autowired一起使用。

@Resource

这个注解属于J2EE,默认使用byName来装配,若不存在与名称匹配的实例,再通过byType来确定。

Resource的name和type属性可指定需要装配的name和type。若指定了name或者type或者两者都指定了,则必须找到唯一的bean进行装配,否则报错。若两者都未指定,则按默认的byName进行装配。

@Autowired和@Resource之间的区别

@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)

@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

AOP

OOP:面向对象编程

AOP:称为面向切面编程,作为OOP的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重复用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。简单来讲就是将纵向重复的代码,横向抽取出来。

大量使用于权限认证、日志、事务处理等。

AspectJ和Spring AOP

AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。

静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

  • AspectJ:是静态代理的增强。静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的就是增强之后的AOP对象。

  • Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

Spring AOP的动态代理

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

  • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

  • 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

Spring采用的是JDK代理和CGLIB代理混合使用。如果被代理对象实现了接口,就优先使用JDK代理,如果没有实现接口,就用cglib代理。

Spring AOP里的名词以及关系

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
  • Target(目标对象):织入 Advice 的目标对象.。
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

Spring知识点小结

可参考博文细说Spring——AOP详解

Advice(增强)的类型以及执行过程

  • before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)

  • after return advice, 在一个 join point 正常返回后执行的 advice

  • after throwing advice, 当一个 join point 抛出异常后执行的 advice

  • after advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.

  • around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.

  • introduction,introduction可以为原有的对象增加新的属性和方法

异常
正常
AOP
around advice
before advice
method
after advice
after throwing advice
after return advice

对于Advice的理解可参考Spring AOP之坑:完全搞清楚advice的执行顺序

Spring的事务

事务就是对一系列的数据库操作(比如插入或修改多条数据)进行统一的提交或回滚操作,如果插入或修改成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。这样可以防止出现脏数据,防止数据库数据出现问题。简单来说就是一个方法内执行了多次数据库操作,要么完全地执行,要么完全地不执行。

现实世界中最常见的事务例子可能就是转账了。

编程式事务管理

编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。

声明式事务管理

声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的;声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。

声明式事务唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。

spring的事务传播行为

当多个事务同时存在的时候,spring如何处理这些事务的行为。

  • PROPAGATION_REQUIRED
    Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行

  • PROPAGATION_REQUES_NEW
    该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可

  • PROPAGATION_SUPPORT
    如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务

  • PROPAGATION_NOT_SUPPORT
    该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码

  • PROPAGATION_NEVER
    该传播机制不支持外层事务,即如果外层有事务就抛出异常

  • PROPAGATION_MANDATORY
    与NEVER相反,如果外层没有事务,则抛出异常

  • PROPAGATION_NESTED
    该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。

事务中有几种隔离级别

事务中会出现脏读、不可重复读、幻读这些并发问题,通过设置隔离级别来解决。

  • ISOLATION_DEFAULT:使用后端数据库默认的隔离级别

  • ISOLATION_READ_UNCOMMITTED:允许读取尚未提交的更改。可能导致脏读、幻读或不可重复读。

  • ISOLATION_READ_COMMITTED(Oracle 默认级别):允许从已经提交的并发事务读取。可防止脏读,但幻读和不可重复读仍可能会发生。

  • ISOLATION_REPEATABLE_READ(MYSQL默认级别):对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻读仍可能发生。

  • ISOLATION_SERIALIZABLE:完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。

参考博文

【1】Spring常见面试题总结

【2】Spring知识点总结

【3】Spring Bean详细讲解 什么是Bean?