SSM框架
Spring概述
什么是Spring
Spring是一个生态,可以构建Java应用所需的一切基础设施,通常Spring是指Spring Framework
Spring是一个轻量级、非入侵式的控制反转(IoC)和面向切面(AOP)的开源容器框架
容器:包含并管理应用对象的生命周期
Spring框架的好处
- 轻量:Spring是轻量的,基本的版本大约2MB
- 控制反转:Spring通过控制反转实现了松散耦合,集中管理对象,方便维护
- 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
- 容器:Spring包含并管理应用中对象的生命周期和配置
- MVC框架:Spring的WEB框架是个精心设计的框架,是Wb框架的一个很好的替代品
- 事务管理:Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)
- 异常处理:Spring提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked异常
Spring有哪些模块
- Spring Core:提供了框架的基本组成部分,包括控制反转(Inversionof Control,IOC)和依赖注入(Dependency Injection,DI)功能
- Spring Beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean
- Spring Context:构建于core封装包基础上的context封装包,提供了一种框架式的对象访问方法
- Spring JDBC:提供了一个JDBC的抽象层,消除了烦琐的]DBC编码和数据库厂商特有的错误代码解析,用于简化JDBC。
- Spring AOP:提供了面向切面的编程实现,让你可以自定义拦截器、切点等
- Spring Web:提供了针对Web开发的集成特性,例如文件上传,利用servlet listeners进行ioc容器初始化和对Web的ApplicationContext
- Spring Test:主要为测试提供支特的,支持使用JUnit或TestNG对Spring细件进行单元试和集成测试
Spring中的设计模式有哪些?
- 工厂模式:Spring容器本质是一个大工厂,使用工厂模式通过BeanFactory、ApplicationContext创建bean对象
- 代理模式:Spring AOP功能功能就是通过代理模式来实现的,分为动态代理和静态代理
- 单例模式:Spring中的Bean默认都是单例的,这样有利于容器对Bean的管理
- 模板模式:Spring中JdbcTemplate、RestTemplate等以Template结尾的对数据库、网络等等进行操作的板类,就使用到了模板模式
- 观察者模式:Spring事件区动模型就是观察者模式很经典的一个应用
- 适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller
- 策略模式:Spring中有一个Resource接口,它的不同实现类,会根据不同的策略去访问资源
Spring IOC
什么是IOC?
是什么:IoC即控制反转(Inversion of Control,缩写为IoC)它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。
干什么:
- 传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来
- 使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面去取即可。
从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)
UserService service = new UserService(); //耦合度太高,维护不方便,想要修改UserService,每一个依赖了UserService的类都得做更改
为什么叫控制反转?
IOC将创建对象的控制权从程序员交给Spring的IOC,程序员使用需要通过依赖注入(DI)
- 控制 :指的是对象创建(实例化、管理)的权力
- 反转 :控制权交给外部环境(IoC 容器)
实现机制: 工厂 + 反射
实现是利用工厂模式,通过反射创建bean
扫描和解析配置文件或注解信息,将其转换为内部的对象定义和依赖关系;
- 在Spring中,可以使用XML文件或注解来配置对象和依赖关系。 Sping通过解析配置文件或注解信息,将其转换为内部的对象定义和依赖关系(BeanDefinition)放到容器(BeanFactory)中。对象定义包括对象的类型、属性、构造函数等信息,依赖关系包括对象之间的依赖关系、依赖注入方式等信息
根据对象定义和依赖关系,使用反射机制动态创建和初始化对象,并将对象注入到需要使用它们的地方。
总的来说,Spring IOC的实现原理是通过反射机制动态创建对象,依赖注入,对象初始化。通过解耦对象之间的依赖关系,使得应用程序更加灵活、可维护、可扩展。
什么是依赖注入(DI)
是什么:
依赖注入是指组件之间的依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中。
干什么:
- 依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台
- 通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现
IoC和DI有什么关系?
IoC和DI都是Spring框架中的核心概念,它们的区别在于:
- IoC(Inverse of Control,控制反转):它是一种思想,主要解决程序设计中的对象依赖关系管理问题。在IoC思想中,对象的创建权反转给第三方容器,由容器进行对象的创建及依赖关系的管理。
- DI(Dependency Injection,依赖注入):它是IoC思想的具体实现方式之一,用于实现IoC。在Spring中,依赖注入是指:在对象创建时,由容器自动将依赖对象注入到需要依赖的对象中。
Spring Bean
什么是Bean
Bean 代指的就是那些被 IoC 容器所管理的对象。
Bean的生命周期
创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。
Bean 属性赋值/填充:为 Bean 设置相关属性和依赖,例如
@Autowired
等注解注入的对象、@Value
注入的值、setter
方法或构造函数注入依赖和值、@Resource
注入的各种资源。Bean 初始化:
Aware 接口的依赖注入
- 如果 Bean 实现了
BeanNameAware
接口,调用setBeanName()
方法,传入 Bean 的名字 - 如果 Bean 实现了
BeanClassLoaderAware
接口,调用setBeanClassLoader()
方法,传入ClassLoader
对象的实例 - 如果 Bean 实现了
BeanFactoryAware
接口,调用setBeanFactory()
方法,传入BeanFactory
对象的实例 - 与上面的类似,如果实现了其他
*.Aware
接口,就调用相应的方法
- 如果 Bean 实现了
- BeanPostProcessor 在初始化前的处理
- 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessBeforeInitialization()
方法
- 如果有和加载这个 Bean 的 Spring 容器相关的
- 在 afterPropertiesSet() 方法写初始化逻辑,指定 init-method 方法,指定初始化方法
- BeanPostProcessor 在初始化后的处理
- 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessAfterInitialization()
方法(打印日志)
- 如果有和加载这个 Bean 的 Spring 容器相关的
- 销毁 Bean:注册相关销毁回调接口,最后通过
DisposableBean
和destory-method
进行销毁- 如果 Bean 实现了
DisposableBean
接口,执行destroy()
方法 - 如果 Bean 在配置文件中的定义包含
destroy-method
属性,执行指定的 Bean 销毁方法。或者,也可以直接通过@PreDestroy
注解标记 Bean 销毁之前执行的方法
- 如果 Bean 实现了
什么是FactoryBean
FactoryBean是Spring所提供的一种较灵活的创建Bean的方式,可以通过实现FactoryBean接口中的getObject()
方法来返回一个对象,这个对象就是最终的Bean对象。
如果一个对象实现了这接口,那它就成为一种特殊的Bean,注册到IOC容器之后,如果调用getBean,Spring会调用FactoryBean的getObject()
方法来获取实际的Bean实例,而不是返回FactoryBean本身。
BeanFactory和FactoryBean区别是什么
BeanFactory和FactoryBean是Spring框架中的两个关键概念,用于创建和管理Bean实例。
- BeanFactory是Spring的基本容器,负责创建和管理Bean实例的;
- 而FactoryBean是一个特殊的Bean,它实现了FactoryBean接口,负责创建其他Bean实例,并提供一些初始化Bean的设置。
下面是BeanFactory和FactoryBean之间的一些关键区别:
- 功能:BeanFactory是一个容器,负责管理Bean的完整生命周期,包括初始化、属性设置、依赖注入和销毁等。FactoryBean是一个接口,定义了创建Bean的规范和逻辑,它负责创建其他Bean实例
- 创建Bean的方式:
- BeanFactory通过配置信息(XML或注解)来创建和管理Bean
- FactoryBean通过实现
getObject()
方法来自定义Bean的创建逻辑
- 灵活性:FactoryBean具有更高的灵活性,因为它允许自定义的逻辑来创建和配置Bean实例。FactoryBean的实现类可以根据特定的条件选择性地创建不同的Bean实例,或者在创建Bean之前进行一些初始化操作。
- 返回类型:
- BeanFactory返回的是Bean实例本身
- 而FactoryBean返回的是由FactoryBean创建的Bean实例
BeanFactoryi和ApplicationContext的关系是什么
总的来说,
- BeanFactory是Spring框架中最基本的容器,提供最基础的IOC和DI的支持;
- 而ApplicationContext是在BeanFactoryl的基础上扩展而来的,提供了更多的功能和特性ApplicationContext是Spring框架中使用较为广泛的容器。
Bean 的作用域有哪些
Spring 中 Bean 的作用域通常有下面几种:
- singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
- prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续
getBean()
两次,得到的是不同的 Bean 实例。 - request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
- websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
Bean是线程安全的吗
Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。默认的是singleton
prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。
不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:
- 在 Bean 中尽量避免定义可变的成员变量;
- 在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)。
将一个类声明为 Bean 的注解有哪些
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用Service
层返回数据给前端页面
@Component 和 @Bean 的区别是什么
@Component
注解作用于类,而@Bean
注解作用于方法。
@Component
使用@ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean
告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。@Bean
注解比@Component
注解的自定义性更强,而且很多地方我们只能通过@Bean
注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring
容器时,则只能通过@Bean
来实现
注入 Bean 的注解有哪些
Spring 内置的 @Autowired
以及 JDK 内置的 @Resource
和 @Inject
都可以用于注入 Bean。
Annotation | Package | Source |
---|---|---|
@Autowired | org.springframework.bean.factory | Spring 2.5+ |
@Resource | javax.annotation | Java JSR-250 |
@Inject | javax.inject | Java JSR-330 |
@Autowired 和 @Resource 的区别是什么
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为byName
(根据名称进行匹配)。- 当一个接口存在多个实现类的情况下,
@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过@Qualifier
注解来显式指定名称,@Resource
可以通过name
属性来显式指定名称。 @Autowired
支持在构造函数、方法、字段和参数上使用。@Resource
主要用于字段和方法上的注入,不支持在构造函数或参数上使用。
@Autowired底层的实现原理是什么
Spring中的@Autowired注解是通过依赖注入(DI)实现的,依赖注入是一种设计模式,它将对象的创建和依赖关系的管理从应用程序代码中分离出来,使得应用程序更加灵活和可维护。
- 具体来说,当Spring容器启动时,它会扫描应用程序中的所有Bean,并将它们存储在一个BeanFactory中。当应用程序需要使用某个Bean时,Spring容器会自动将该Bean注入到应用程序中。
- 但再往底层说,DI是通过Java反射机制实现的。当Spring容器需要注入某个Bean时,它会使用Java反射机制来查找符合条件的Bean,并将其注入到应用程序中。
所以说,@Autowired注解是通过DI的方式,底层通过Java的反射机制来实现的。
Spring如何解决循环依赖问题
Spring循环依赖问题指的是在Spring容器中出现相互依赖的情况,即两个或多个Bean之间相互依赖,形成了一个循环依赖链。例如,Bean A依赖Bean B,Bean B又依赖Bean A,这就构成了一个循环依赖。
Spring是通过三级缓存解决循环依赖问题的,基本思路是:在Bean创建过程中,将正在创建的Bean对象放入一个专门用于缓存正在创建中的Bean对象的缓存池中,当后续创建其他Bean对象时,若需要依赖于该缓存池中正在创建的Bean,则直接使用缓存池中的Bean对象,而不是重新创建一个新的Bean对象。
简单来说,Spring 的三级缓存包括:
- 一级缓存(singletonObjects):存放最终形态的 Bean(已经实例化、属性填充、初始化),单例池,为“Spring 的单例属性”⽽⽣。一般情况我们获取 Bean 都是从这里获取的,但是并不是所有的 Bean 都在单例池里面,例如原型 Bean 就不在里面。
- 二级缓存(earlySingletonObjects):存放过渡 Bean(半成品,尚未属性填充),也就是三级缓存中
ObjectFactory
产生的对象,与三级缓存配合使用的,可以防止 AOP 的情况下,每次调用ObjectFactory#getObject()
都是会产生新的代理对象的(如果B,C都依赖A,没有二级缓存会创建两个代理对象)。 - 三级缓存(singletonFactories):存放
ObjectFactory
,ObjectFactory
的getObject()
方法(最终调用的是getEarlyBeanReference()
方法)可以生成原始 Bean 对象或者代理对象(如果 Bean 被 AOP 切面代理)。- 不会立即调:(如果在实例化后立即调用的话:所有的aop不管bean是否循环依赖都会在实例化后创建proxy,正常Bean其实spring还是希望遵循生命周期在初始化创建动态代理,只能循环依赖才创建)
- 会在ABA(第二次getBean(A)才会去调用三级缓存(如果实现了aop才会创建动态代理,如果没有实现依然返回的Bean的实例))
- 放入二级缓存(避免重复创建)
- 三级缓存只会对单例 Bean 生效。
具体而言,Spring通过三级缓存解决循环依赖问题的步骤如下:
- 当 Spring 创建 A 之后,发现 A 依赖了 B ,又去创建 B,B 依赖了 A ,又去创建 A;
- 在 B 创建 A 的时候,那么此时 A 就发生了循环依赖,由于 A 此时还没有初始化完成,因此在 一二级缓存 中肯定没有 A;
- 那么此时就去三级缓存中调用
getObject()
方法去获取 A 的 前期暴露的对象 ,也就是调用上边加入的getEarlyBeanReference()
方法,生成一个 A 的 前期暴露对象(原始 Bean 对象或者代理对象); - 然后就将这个
ObjectFactory
从三级缓存中移除,并且将前期暴露对象放入到二级缓存中,那么 B 就将这个前期暴露对象注入到依赖,来支持循环依赖。
需要注意的是,三级缓存并不是无限制地缓存Bean对象,而是限定在Bean对象创建过程中使用,Bean对象创建完成后将会从三级缓存中移除。此外,如果Bean对象的依赖关系存在循环依赖,则在创建过程中将会抛出异常,因为无法通过缓存解决循环依赖的问题
只用两级缓存够吗?
- 如果只是死循环的问题:一级缓存就可以解决:无法避免在并发下获取不完整的Bean?
- 只使用一级和三级缓存也可以解决循环依赖:只不过如果出现重复循环依赖会多次创建AOP的动态代理
Spring有没有解决多例Bean的循环依赖?
- 多例不会使用缓存进行存储(多例Bean每次使用都需要重新创建)
- 不缓存早期对象就无法解决循环
Spring有没有解决构造函数参数Bean的循环依赖?
没有,可以通过人工进行解决:@Lazy
- 不会立即创建依赖的bean
- 而是等到用到才通过动态代理进行创建
Spring:是如何帮我们在并发下避免获取不完整的Bean?
不完整Bean:只是实例化,没有属性填充,初始化
如果不加锁,线程1实例化加入三级缓存,线程2直接拿三级缓存,就不完整
使用双重检查锁,检查完一级缓存后加锁,放锁后再检查一次,因为放锁后已经创建好的bean放在一级缓存但是第一次已经检查过,所以得双重检查
Spring中可以出现两个ID相同的bean吗,如果不行会在什么时候报错
分情况,同一个spring配置文件里不能存在id相同的bean,会在解析xml文件转换为BeanDefinition阶段报错。不同的spring配置文件里可以存在id相同的两个bean,默认会把多个id相同的bean进行覆盖,spring3.X版本后使用@Configuration进行配置的时候,同一个配置类中使用@Bean声明多个相同名字的bean默认只会注册第一个,使用@Autowired可能会提示找不到未注册的类,使用@Resource注解会在bean初始化之后依赖注入的时候可能会提示类型不匹配错误
Spring提供了哪些配置方式
将Spring配置到应用开发中有以下三种方式:
基于XML的配置
bean所需的依赖项和服务在XML格式的配置文件中指定。这些配置文件通常包含许多bean定义和特定于应用程序的配置选项。它们通常以bean标签开头。例如:
<bean id="studentbean"class="org.edureka.firstSpring.StudentBean"> <property name="name"value="Edureka"></property> </bean>
基于注解的配置
- 可以通过在相关的类,方法或字段声明上使用注解,将bean配置为组件类本身
基于Java API配置
Spring的Java配置是通过使用@Bean和@Configuration来实现
@Bean注解扮演与
元素相同的角色。 @Configuration类允许通过简单地调用同一个类中的其他@Bean方法来定义bean间依赖关系。 例如:
@Configuration public class StudentConfig{ @Bean public StudentBean mystudent(){ return new studentBean(); } }
- 作用范围:
@Bean
注解用于标记方法,表示该方法将返回一个对象,这个对象要注册为 Spring 容器中的 Bean。@Configuration
注解用于标记类,表示该类是一个配置类,里面可以包含多个使用@Bean
注解的方法。- 代理机制:
@Configuration
类会被 Spring 特殊处理,生成一个CGLIB代理对象,当在@Configuration
类中调用@Bean
方法时,Spring 会用代理对象来拦截方法调用,确保返回的是单例 Bean。@Bean
方法如果不在@Configuration
类中使用,则不会被 CGLIB 代理,也就没有这种单例 Bean 的保证。- 注入依赖:
- 在
@Configuration
类中,@Bean
方法可以接受参数,Spring 会自动根据参数类型注入依赖的 Bean。- 在普通类中使用
@Bean
注解,由于没有 CGLIB 代理,方法参数需要手动注入依赖 Bean。- 使用场景:
@Configuration
类通常用于替代 XML 配置文件,集中管理应用程序的 Bean 定义。@Bean
注解则可以单独使用,在任何类中定义 Bean,比如在 service 层或 controller 层。总的来说,
@Configuration
是一种特殊的配置类,它能够确保@Bean
方法返回的 Bean 是单例的,并且可以方便地管理 Bean 之间的依赖关系。而单独使用@Bean
注解则更灵活,可以在任意位置定义 Bean,但需要自己管理依赖关系。
什么是Spring的内部bean
Spring 中的内部 bean (Inner Bean) 是指在另一个 bean 的内部定义的 bean。它通常用于属性注入中,作为某个属性的值。
内部 bean 有以下特点:
- 定义位置: 内部 bean 是定义在另一个 bean 的内部,通常是作为某个属性的值。
- 作用域限制: 内部 bean 的作用域仅限于包含它的外部 bean,它没有独立的 bean 标识。
- 匿名: 内部 bean 通常是匿名的,没有显式的 bean 标识。
- 生命周期: 内部 bean 的生命周期与包含它的外部 bean 实例的生命周期相同。
什么是Spring装配
Spring 装配(Wiring)是指在 Spring 容器中,定义 bean 之间的依赖关系和注入方式的过程。它是 Spring IoC 容器的核心功能之一。
Spring 装配主要包括以下几个方面:
- Bean 定义: 通过 XML、Java 注解或 Java 配置类等方式,定义 bean 及其属性。
- 依赖注入: 确定 bean 与 bean 之间的依赖关系,并通过构造器注入、setter 注入或字段注入等方式将依赖注入到 bean 中。
- 自动装配: 容器可以自动识别 bean 之间的依赖关系,并自动完成注入,无需显式配置。
- Bean 作用域: 确定 bean 的作用域,如 singleton、prototype 等。
- 生命周期回调: 定义 bean 的初始化和销毁方法。
自动装配有哪些方式?
Spring容器能够自动装配bean。也就是说,可以通过检查BeanFactory的内容让Spring自动解析bean的协作者。 自动装配的不同模式:
no,这是默认设置,表示没有自动装配。应使用显式beān引用进行装配。
byName,它根据bean的名称注入对象依赖项。它匹配并装配其属性与XML文件中由相同名称定义的bean
byType,它根据类型注入对象依赖项。如果属性的类型与XML文件中的一个bean名称匹配,则匹配并装配属 性。
构造器,它通过调用类的构造器来注入依赖项。它有大量的参数。
autodetect首先容器尝试通过构造器使用autowire装配,如果不能,则尝试通过byType自动装配。
自动装配有什么局限?
- 覆盖的可能性-您始终可以使用<constructor-arg>和
设置指定依赖项,这将覆盖自动装配。 - 基本元数据类型简单属性(如原数据类型,字符串和类)无法自动装配。
- 令人困惑的性质,总是喜欢使用明确的装配,因为自动装配不太精确。
Spring AOP
什么是AOP
是什么:AOP(Aspect-Oriented Programming),即面向切面编程,它与OOP(Object-Oriented Programming,面向对象编程)相辅相成,提供了与OOP不同的抽象软件结构的视角。
在OOP中,我们以类(class)作为我们的基本单元,而AOP中的基本单元是Aspect(切面)。
干什么:AOP(Aspect-Oriented Programming面向切面编程)通过代理的方式,在调用想要的对象方法时候,进行拦截处理,执行切入的逻辑,能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
怎么做:AOP采用代理模式实现,动态切入。 实现上代理大体上可以分为:动态代理和静态代理。
静态代理,代理类在编译阶段生成,在编译阶段将通知织入Java字节码中,也称编译时增强。典型的AspectJ就是静态代理。
- 缺点:对于每一个目标接口或类,都需要定义一个对应的代理类,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理,代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。动态代理:动态代理主要有两种实现方式:
- JDK动态代理:JDK动态代理要求被代理的类必须实现一个接口,它通过反射来接收被代理的类,并使用 InvocationHandler接口和Proxy类实现代理
- CGLIB动态代理:CGLIB则是一个代码生成的类库,它可以在运行时动态地生成某个类的子类,通过继承 的方式实现代理。如果目标类没有实现接口,Spring AOP会选择使用CGLIB来动态代理目标类
Spring AOP
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。
当然也可以使用 AspectJ !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
Spring AOP主要由以下概念组成:
术语 | 含义 |
---|---|
目标(Target) | 被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
通知(Advice) | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
切面(Aspect) | 切入点(Pointcut)+通知(Advice) |
Weaving(织入) | 将通知应用到目标对象,进而生成代理对象的过程动作 |
Spring AOP 和 AspectJ AOP 有什么区别
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ 。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
AspectJ 定义的通知类型有哪些?
- Before(前置通知):目标对象的方法调用之前触发
- After (后置通知):目标对象的方法调用之后触发
- AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
- AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值
- Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
Spring MVC
MVC是什么?MVC设计模式的好处有哪些
Spring MVC是一个基于Java实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把模型(model)-视图(view)-控制器(controller)分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
流程:
- 用户通过View页面向服务端提出请求,可以是表单请求、超链接请求、AJAX请求等;
- 服务端Controller控制器接收到请求后对请求进行解析,找到相应的Model,对用户请求进行处理Mode!处理;
- 将处理结果再交给Controller(控制器其实只是起到了承上启下的作用);
- 根据处理结果找到要作为向客户端发回的响应View页面,页面经渲染后发送给客户端。
MVC设计模式的好处:
- 分层设计,实现了业务系统各个组件之间的解耦,有利于业务系统的可扩展性,可维护性;
- 有利于系统的并行开发,提升开发效率。
Spring MVC常用的注解有哪些
@RequestMapping:用于处理请求url映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径
- @RequestBody:注解实现接收http请求的json数据,将json转换为java对象
- @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户
- @Controller:控制器的注解,不能用用别的注解代替
- @RestController:@ResponseBody + @Controller
Spring MVC 的核心组件有哪些,执行流程了解吗
记住了下面这些组件,也就记住了 SpringMVC 的工作原理。
DispatcherServlet
:核心的中央处理器,负责接收请求、分发,并给予客户端响应。Handler
:请求处理器,完成具体业务逻辑。HandlerMapping
:处理器映射器,根据 URL 去匹配查找能处理的Handler
,并会将请求涉及到的拦截器和Handler
一起封装。HandlerAdapter
:处理器适配器,根据HandlerMapping
找到的Handler
,适配执行对应的Handler
;HandlerInterceptor
:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。HandlerExecutionChain
:处理器执行链,包括两部分内容:Handler
和HandlerInterceptor
(系统会有一个默认的HandlerInterceptor
,如果需要额外设置拦截,可以添加拦截器)。ModelAndView
:装载了模型数据和视图信息,作为Handler
的处理结果,返回给DispatcherServlet
ViewResolver
:视图解析器,根据Handler
返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给DispatcherServlet
响应客户端
SpringMVC是基于MVC设计模式实现的Web框架,其工作流程如下:
- 客户端发送HTTP请求至前端控制器
DispatcherServlet
DispatcherServlet
根据请求信息调用HandlerMapping
,解析请求对应的Handler
即处理器(Controller)HandlerMapping
根据请求URL查找对应的Controller,同时生成用于执行该请求的HandlerExecutionChain
对象DispatcherServlet
调用HandlerAdapter
执行Handler
Handler
执行完成后,返回一个ModelAndView
对象给HandlerAdapter
HandlerAdapter
将ModelAndView
对象传递给DispatcherServlet
DispatcherServlet
调用ViewResolver
解析视图(View)ViewResolver
解析出View
对象后,将其返回给DispatcherServlet
DispatcherServlet
调用View
对象的render()方法进行视图渲染DispatcherServlet
将渲染后的视图(生成好的HTML内容)返回给客户端
在这个过程中,DispatcherServlet
是整个SpringMVC的核心,它负责协调各个组件的工作。HandlerMapping
负责将请求映射到对应的Controller,而HandlerAdapter
负责执行Controller。ViewResolver
则根据逻辑视图名(如JSP文件名)解析出View对象,最后由View渲染出实际的页面内容。通过这种分工协作的方式,SpringMVC可以实现灵活、高效、可扩展的Web应用程序开发。
Spring MVC拦截器是什么
Spring的处理程序映射机制包括处理程序拦截器,当你希望将特定功能应用于某些请求时,例如,检查用户主题时,这些拦截器非常有用。 拦截器必须实现org.springframework.web.servlet包的HandlerInterceptor。此接口定义了三种方法:
- preHandle:在执行实际处理程序之前调用。
- postHandle:在执行完实际程序,之后调用。
- afterCompletion:在完成请求后调用。
使用场景:
- 日志记录:可用于记录请求日志,便于信息监控和信息统计;
- 权限检查:可用于用户登录状态的检查;
- 统一安全处理:可用于统一的安全效验或参数的加密/解密等。
拦截器和过滤器区别是什么
拦截器和过滤器的区别主要体现在以下5点:
- 出身不同:过滤器来自于Servlet,而拦截器来自于Spring框架;
- 触发时机不同:请求的执行顺序是:请求进入容器>进入过滤器>进入Servlet>进入拦截器>执行控制器(Controller),所以过滤器和拦截器的执行时机,是过滤器会先执行,然后才会执行拦截器,最后才会进入真正的要调用的方法;
- 底层实现不同:过滤器是基于方法回调实现的,拦截器是基于动态代理(底层是反射)实现的;
- 支持的项目类型不同:过滤器是Servlet规范中定义的,所以过滤器要依赖Servlet容器,它只能用在Web项目中;而拦截器是Spring中的一个组件,因此拦截器既可以用在Web项目中,同时还可以用在Application或Swing程序中;
- 使用的场景不同:因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:登录判断、权限判断、日志记录等业务;而过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、字符集编码设置、响应数据压缩等功能。
Spring MVC统一异常处理
@ControllerAdvice
+ @ExceptionHandler
,会给所有或者指定的 Controller
织入异常处理的逻辑(AOP),当 Controller
中的方法抛出异常的时候,由被@ExceptionHandler
注解修饰的方法进行处理。
Spring事务
Spring事务实现方式有哪些?
- 编程式事务:在代码中硬编码(在分布式系统中推荐使用):通过TransactionTemplate或者TransactionManager手动管理事务,事务范围小。
- 声明式事务:在XML配置文件中配置或者直接基于注解(单体应用或者简单业务系统推荐使用):实际是通过AOP实现(基于@Transactional的全注解方式使用最多)
事务的传播级别有哪些?
事务的传播机制定义了在方法被另一个事务方法调用时,这个方法的事务行为应该如何。
Spig提供了一系列事务传播行为,这些传播行为定义了事务的边界和事务上下文如何在方法调用链中传播。
Spring事务定义了7种传播机制:
- PROPAGATION_REQUIRED:表示如果当前存在事务,则在当前事务中执行;如果当前没有事务,则创建一个新的事务并在其中执行。即,方法被调用时会尝试加入当前的事务,如果不存在事务,则创建一个新的事务。如果外部事务回滚,那么内部事务也会被回滚(一句话:方法调用将加入当前事务,或者创建一个新事务)
- PROPAGATION_NESTED:表示如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务并在其中执行。嵌套事务是独立于外部事务的子事务,它具有自己的保存点,并且可以独立于外部事务进行回滚。如果嵌套事务发生异常并回滚,它将会回滚到自己的保存点,而不影响外部事务(一句话:是嵌套的传播行为,方法调用将在独立的子事务中执行,具有自己的保存点,可以独立于外部事务进行回滚,而不影响外部事务)
- 如果希望内部方法能够独立于外部事务进行回滚,可以选择PROPAGATION_NESTED,如果你希望内部方法与外部事务一同回滚或提交,可以选择PROPAGATION_REQUIRED。
Spring事务中的隔离级别有哪几种?
在TransactionDefinition接口中定义了五个表示隔离级别的常量:
- ISOLATION_DEFAULT:使用后端数据库默认的隔离界别,MySQL默认可重复读,Oracle默认读已提交
- ISOLATION_READ_UNCOMMITTED:读未提交,最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
- ISOLATION_READ_COMMITTED:读已提交,允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- ISOLATION_REPEATABLE_READ:可重复读,对同一字段的多次读取结果都是一致的,除非数据是被本身事务 自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
- ISOLATION_SERIALIZABLE:串行化,最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
声明式事务实现原理了解吗?
Spring的声明式事务管理是通过AOP(面向切面编程)和代理机制实现的
第一步,在Bean初始化阶段创建代理对象: Spring容器在初始化单例Bean的时候,会遍历所有的BeanPostProcessor实现类,并执行其postProcessAfterInitialization方法。
在执行postProcessAfterInitialization方法时会遍历容器中所有的切面,查找与当前Bean匹配的切面,这里会获取事务的属性切面,也就是@Transactional注解及其属性值。
然后根据得到的切面创建一个代理对象,默认使用JDK动态代理创建代理,如果目标类是接口,则使用JDK动态代理,否则使用Cglib。
第二步,在执行目标方法时进行事务增强操作: 当通过代理对象调用Bean方法的时候,会触发对应的AOP增强拦截器,声明式事务是一种环绕增强,对应接口为MethodInterceptor,事务增强对该接口的实现为TransactionInterceptor,
事务拦截器TransactionInterceptor在invoke方法中,通过调用父类TransactionAspectSupport的invokeWithinTransaction方法进行事务处理,包括开启事务、事务提交、异常回滚等。
Spring声明式事务无效可能的原因有哪些?
在开发过程中,可能会遇到使用@Transactional进行事务管理时出现失效的情况。这里是基于事务的默认传播行为是REQUIRED
常见失效场景:
- 如果使用MySQL且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,改成InnoDB引擎则支持事务。
- 注解@Trasactional只能加在public修饰的方法上事务才起效。如果加在protect、private等非public修饰的方法上,事务将失效。
- 原因:Spring默认使用基于JDK的动态代理(当接口存在时)或基于CGLIB的代理(当只有类时)来实现事务。这两种代理机制都只能代理公开的方法。
- 如果在开启了事务的方法内,使用了try-catch语句块对异常进行了捕获,而没有将异常抛到外层,事务将不起效(事务管理依赖于异常传播机制,无法感知异常)。
- 不同类之间方法调用时,只有异常发生在开启事务的方法内,事务才有效。
- 在同一个类的方法之间调用中,如果A方法调用了B方法,不管A方法有没有开启事务,由于Sping的代理机制(同一个类的两个方法之间互相调用时,AOP代理就失效了)B方法的事务是无效的
Spring注解
用过哪些重要的Spring注解?
- @Controller用于Spring MVC项目中的控制器类。
- @Service用于服务类。
- @RequestMapping用于在控制器处理程序方法中配置URI映射。
- @ResponseBody用于发送Object作为响应,通常用于发送XML或JSON数据作为响应。
- @PathVariable用于将动态值从URI映射到处理程序方法参数。
- @Autowired用于在spring bean中自动装配依赖项。
- @Qualifier使用@Autowired注解,以避免在存在多个bean类型实例时出现混淆。
- @Scope用于配置spring bean的范围。
- @Configuration,@ComponentScan和@Bean用于基于java的配置.
- @Aspect,@Before,@Ater,@Around,@Pointcut用于切面编程(AOP)
@Component,@Controller,@Repository,@Service有何区别?
- @Component:这将java类标记为bean。它是任何Spring管理组件的通用构造型。spring的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
- @Controller:这将一个类标记为Spring Web MVC控制器。标有它的Bean会自动导入到IoC容器中。
- @Service:此注解是组件注解的特化。它不会对@Component注解提供任何其他行为。您可以在服务层类中使用@Service而不是@Component,因为它以更好的方式指定了意图。
- @Repository:这个注解是具有类似用途和功能的@Component注解的特化。它为DAO提供了额外的好处。它将DAO导入IoC容器,并使未经检查的异常有资格转换为Spring DataAccessException。
@Qualifier注解有什么用?
多个相同类型的bean注入冲突,使用@Qualifier注解和@Autowired通过指定应该装配哪个确切的bean来消除歧义。
相同类型的bean指的是多个bean实例实现了同一个接口或继承自同一个抽象类
@RequestMapping和@GetMapping注解的不同之处在哪里?
- @RequestMapping:可注解在类和方法上;@GetMapping仅可注册在方法上
- @RequestMapping:可进行GET、POST、PUT、DELETE等请求方法;@GetMapping是@RequestMapping的GET请求方法的特例,目的是为了提高清晰度
@Controller注解有什么用?
@Controller注解标记一个类为Spring Web MVC控制器Controller。Spring MVC会将扫描到该注解的类,然后扫描这个类下面带有@RequestMapping注解的方法,根据注解信息,为这个方法生成一个对应的处理器对象。
@RestController和@Controller有什么区别?
@RestController注解,在@Controller基础上,增加了@ResponseBody注解,更加适合目前前后端分离的架构下,提供Restful API,返回
例如JSON数据格式。当然,返回什么样铂的数据格式,根据客户端的ACCEPT请求头来决定。
@RequestParam和@PathVariable两个注解的区别?
两个注解都用于方法参数,获取参数值的方式不同,@RequestParam注解的参数从请求携带的参数中获取,而@PathVariable注解从请求的URI中获取