Spring知识点小结

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——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?

发布了11 篇原创文章 · 获赞 1 · 访问量 437

猜你喜欢

转载自blog.csdn.net/xiaoHui_1126/article/details/104740976