文章目录
- 1、谈谈你对Spring的理解?
- 1.1 发展历程
- 1.2 Spirng的组成
- 1.3 Spring的好处
- 2、Autowired和Resource的区别
- 2.1 共同点:
- 2.2 Autowired
- 2.3 Resource
- 2.3.1 @Resource的装配顺序
- 3、Spring常用注解
- 3.1、给容器中注入组件
- 3.1.1 包扫描+组件标注注解
- 3.1.2 @Bean
- 3.1.3 @Import
- 3.1.3 使用spring提供的FactoryBean(工厂Bean)
- 3.2、注入bean的注解
- 3.3、@JsonIgnore
- 3.4、初始化和销毁方法
- 3.5、Java配置类相关注解
- 3.6、切面(AOP)相关注解
- 3.7、@Bean的属性支持
- 3.8、@Value注解
- (1)支持如下方式的注入:
- (2)@Value三种情况的用法。
- 3.9、环境切换
- 3.10、异步相关
- 3.11、定时任务相关
- 3.12、Enable***注解说明
- 3.13、测试相关注解
- 3.14、@SuppressWarnings
- 4、SpringMVC常用注解
- 4.1、@EnableWebMvc
- 4.2、@Controller
- 4.3、@RequestMapping
- 4.4、@ResponseBody
- 4.5、@RequestBody
- 4.6、@PathVariable
- 4.7、@RestController
- 4.8、@ControllerAdvice
- 4.9、@ExceptionHandler
- 4.10、@InitBinder
- 4.11、@ModelAttribute
- 4.12、@Transactional
- 4.12.1 @Transactional 注解的属性信息
- 5、循环依赖
- 5.1 本质
- 5.2 Spring中是如何解决循环依赖的?
- 5.3 Spring中构造注入的循环依赖问题
- 6、Spring Bean的生命周期
- (1)实例化Bean:
- (2)设置对象属性(依赖注入):
- (3)处理Aware接口:
- (4)BeanPostProcessor前置处理:
- (5)InitializingBean:
- (6)init-method:
- (7)BeanPostProcessor后置处理:
- (8)DisposableBean:
- (9)destroy-method:
- 7、Spring支持几种作用域?
- 8、Spring事务的隔离级别
- 8.1 Spring支持的隔离级别
- 9、Spring中的事务传播属性
- 9.1 PROPAGATION_REQUIRED
- 9.2.PROPAGATION_sUPPORTS
- 9.3PROPAGATION_MANDATORY
- 9.4.PROPAGATION REQUIRES NEW
- 10、Spring中事务实现方式
- 11、 事务的本质
- 11.1 事务JDBC原生实现方式
- 11.2 编程式事务
- 11.3 声明式事务
- 11.4 Spring事务回滚
- 12、@Transactional的使用
- 12.1 常用参数
- 12.1.1 传播行为参数
- 12.1.2 超时参数
- 12.1.3 隔离级别参数
- 12.1.4 常用参数汇总
- 12.1.5 事务的嵌套失效
- 13、SpringMVC的理解
- 14、SpringBoot的理解
- 14.1 自动装配
- 启动类:
- @SpringBootApplication注解:
- @EnableAutoConfiguration:
- 15、@Import注解的理解
- 16、SpringBoot自动装配中为什么用DeferredImportSelector
- 17、Bootstrap.yml的作用
- 18、如何对属性文件中的账号密码加密?
- Run方法:
- 19、@Indexed注解的理解
- 20、@Component、@Controller、@Repository、@Service
- 21、Spring中的AOP有哪些通知类型(Advice)?
- 21.1 Advice的类型
- 21.2、Advice的执行顺序:
- 22、Spring的依赖注入
- 23、Spring的IOC理解
- 23.1 什么是IOC:
- 23.2 什么是DI:
- 23.3 IoC的原理:
- 24、Spring的AOP理解:
- 24.1 静态代理:
- 24.2 动态代理:
- 24.3 区别:
- **25、Spring AOP里面的几个名词的概念:**
- (1)连接点(Join point):
- (2)切面(Aspect):
- (3)切点(Pointcut):
- (4)通知(Advice):
- (5)目标对象(Target):
- (6)织入(Weaving):
- (7)引入(Introduction):
- 26、Spring容器的启动流程:
- 26.1 初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中:
- 26.2 将配置类的BeanDefinition注册到容器中:
- 26.3 调用refresh()方法刷新容器:
- 27、Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?
- 28、Spring 框架中都用到了哪些设计模式?
- 29、注解的原理:
- (1)什么是注解:
- (2)如何自定义注解?
1、谈谈你对Spring的理解?
一个轻量级的IOC和AOP容器框架,是为Java应用程序提供基础性服务的一套框架。
目的是用于简化企业级应用程序的开发,使开发者只需要关心业务需求。
- 常见的三种配置方式:
-
- 基于XML的配置;
- 基于注解的配置;
- 基于Java的配置。
1.1 发展历程
1.2 Spirng的组成
Spring Core:核心类库,提供IOC服务;
Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDL、定时任务等);
Spring AOP:AOP服务;
Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;
Sping ORM:对现有的ORM框架的支持;
Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;
Spring MVC:提供面向Web应用的Model-View-Controller实现。
1.3 Spring的好处
好处 | 说明 |
---|---|
轻量 | 基础版本大约2MB |
控制反转 | 通过控制反转实现了松散耦合,对象们给出他们的依赖而不是创建或查找依赖的对象们。 |
面向切面编程(AOP) | 支持面向切面编程,把应用业务逻辑和系统服务分开。 |
容器 | 包含并管理应用中对象的生命周期和配置。 |
MVC框架 | Spring的WEB框架是个精心设计的框架,是WEB框架的一个很好的替代品。 |
事务管理 | 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA) |
异常处理 | 提供方便的API把具体技术相关的异常转化为一致的unchecked异常。 |
最重要的 | 用的人多 |
2、Autowired和Resource的区别
2.1 共同点:
两者都是在做Bean的注入的时候
都是写在字段和setter方法上,两者如果都写在字段上,则不需要再写setter方法。
Autowired | Resource | |
---|---|---|
出处 | Spirng提供 org.springframework.beans.factory.annotation.Autowired | jdk提供 javax.annotation.Resource; |
2.2 Autowired
@Service
public class TestServiceImpl implements TestService {
//使用一种即可
@Autowired
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao){//用于属性的方法上
this.userDao = userDao;
}
}
Autowired是按照类型(byType)装配依赖对象。默认情况下要求依赖对象必须存在。
如果运行null值,可以设置它的required熟悉为false。
如果我们想要使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。
@Autowired
@Qualifier("userDao")
private UserDao userDao;
2.3 Resource
Resource默认按照(ByName)自动注入,由J2EE提供,需导入javax.annotation.Resource。
有两个重要属性:name和type。
- 而Spring将@Resource注解的name属性解析为Bean的名字,而type属性则解析为bean的类型。
- 如果要使用name属性,则使用byName的自动注入策略,
- 使用type属性时,则使用byType自动注入策略。
- 如果两者都不制定,则通过反射机制使用byName自动注入策略。
2.3.1 @Resource的装配顺序
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
- 如果制定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
- 如果制定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或找到多个,都会抛出异常。
- 如果既没有指定name,也没有指定type,则自动按照byName方式进行装配;没有匹配则回退为一个原始类型进行装配,匹配则自动装配。
@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。
3、Spring常用注解
3.1、给容器中注入组件
3.1.1 包扫描+组件标注注解
@Component:泛指各种组件
@Controller、@Service、@Repository都可以称为@Component。
@Controller:控制层
@Service:业务层
@Repository:数据访问层
3.1.2 @Bean
导入第三方包里面的注解
3.1.3 @Import
@Import(要导入到容器中的组件);
- import标签的替换,也是Enable***的前置基础。
- 可以不需要实例化,直接被Spring注入。
@Import({Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig2 {
@Scope("prototype")
@Bean("person")
public Person person() {
System.out.println("我是Person");
return new Person("素小暖",25);
}
}
@ImportSelector:返回需要导入的组件的全类名数组;
public class MyImportSelector implements ImportSelector {
//返回值就是导入容器的组件全类目
// AnnotationMetadata 当前标注@Import注解的类的所有注解信息
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//importingClassMetadata.get
return new String[]{"com.atguigu.bean.Blue","com.atguigu.bean.Red"};
}
}
@ImportBeanDefinitionRegistrar:手动注册bean到容器中;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/*
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition注册类
* 把所有需要添加到容器中的bean,调用BeanDefinitionRegistry.registerBeanDefinition手动注入
*
* */
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("com.atguigu.bean.Red");
boolean definition2 = registry.containsBeanDefinition("com.atguigu.bean.Blue");
if(definition && definition2){
//指定bean定义信息(bean的类型,bean的scope)
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
//注册一个bean,指定bean名
registry.registerBeanDefinition("rainBow",rootBeanDefinition);
}
}
}
3.1.3 使用spring提供的FactoryBean(工厂Bean)
- 默认获取到的是工厂Bean调用getObject创建的对象
- 要获取工厂Bean本身,需要在id前面加一个&
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
//创建一个spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {
//返回一个Color对象,并将Color添加到容器中
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean,getObject()");
return new Color();
}
public Class<?> getObjectType() {
return Color.class;
}
public boolean isSingleton() {
return false;
}
}
3.2、注入bean的注解
@Autowired:由bean提供
@Autowired可以作用在变量、setter方法、构造函数上;
有个属性为required,可以配置为false;
@Inject:由JSR-330提供
@Inject用法和@Autowired一样。
@Resource:由JSR-250提供
@Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的,@Autowired如果需要按照名称匹配需要和@Qualifier一起使用,@Inject和@Name一起使用。
@Primary
让spring进行自动装配的时候,默认使用首选的bean,和@Qualifier一个效果。
3.3、@JsonIgnore
(1)作用
在json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响。
(2)使用方法
一般标记在属性或者方法上,返回的json数据即不包含该属性。
(3)注解失效
如果注解失效,可能是因为你使用的是fastJson,尝试使用对应的注解来忽略字段,注解为:@JSONField(serialize = false),使用方法一样。
3.4、初始化和销毁方法
(1)通过@Bean(initMethod=“init”,destoryMethod=“destory”)方法
(2)通过bean实现InitializingBean来定义初始化逻辑,DisposableBean定义销毁逻辑
(3)可以使用JSR250:@PostConstruct:初始化方法;@PreDestory:销毁方法。
(4)BeanPostProcessor:bean的后置处理器,在bean初始化前后进行一些处理工作
postProcessBeforeInitialization:在初始化之前工作;
postProcessAfterInitialization:在初始化工作之后工作;
3.5、Java配置类相关注解
@Configuration
声明当前类为配置类;
@Bean
注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式;
@ComponentScan
用于对Component进行扫描;
@indexd 提升 @ComponentScan的效率
- 项目编译打包时,会在自动生成META-INF/spring.components文件,文件包含被@Indexed注释的类的模式解析结果。
- 当Spring应用上下文进行组件扫描时,META-INF/spring.components会被org.springframework.context.index.CandidateComponentsIndexLoader读取并加载,转换为CandidateComponentsIndex对象,此时组件扫描会读取CandidateComponentsIndex,而不进行实际扫描,从而提高组件扫描效率,减少应用启动时间。
3.6、切面(AOP)相关注解
Spring支持AspectJ的注解式切面编程。
-
@Aspect 声明一个切面
-
@After 在方法执行之后执行(方法上)
-
@Before 在方法执行之前执行(方法上)
-
@Around 在方法执行之前与之后执行(方法上)
-
@PointCut 声明切点
-
在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持
3.7、@Bean的属性支持
@Scope设置类型包括:
设置Spring容器如何新建Bean实例(方法上,得有@Bean)
① Singleton
(单例,一个Spring容器中只有一个bean实例,默认模式),
② Protetype
(每次调用新建一个bean),
③ Request
(web项目中,给每个http request新建一个bean),
④ Session
(web项目中,给每个http session新建一个bean),
⑤ GlobalSession
(给每一个 global http session新建一个Bean实例)
3.8、@Value注解
(1)支持如下方式的注入:
- 注入普通字符
- 注入操作系统属性
- 注入表达式结果
- 注入其它bean属性
- 注入文件资源
- 注入网站资源
- 注入配置文件
@Value("${phone.zyw:15002711507}")
private String phone;
@Value("17771001423")
private String ShPhone;
(2)@Value三种情况的用法。
- ${}是去找外部配置的参数,将值赋过来
- #{}是SpEL表达式,去寻找对应变量的内容
- #{}直接写字符串就是将字符串的值注入进去
3.9、环境切换
@Profile
指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件。
@Conditional
通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。
3.10、异步相关
@EnableAsync
配置类中通过此注解开启对异步任务的支持;
@Async
在实际执行的bean方法使用该注解来声明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)
3.11、定时任务相关
@EnableScheduling
在配置类上使用,开启计划任务的支持(类上)
@Scheduled
来申明这是一个任务,包括cron,fixDelay,fixRate等类型(方法上,需先开启计划任务的支持)
3.12、Enable***注解说明
这些注解主要是用来开启对xxx的支持:
- @EnableAspectAutoProxy:开启对AspectJ自动代理的支持;
- @EnableAsync:开启异步方法的支持;
- @EnableScheduling:开启计划任务的支持;
- @EnableWebMvc:开启web MVC的配置支持;
- @EnableConfigurationProperties:开启对@ConfigurationProperties注解配置Bean的支持;
- @EnableJpaRepositories:开启对SpringData JPA Repository的支持;
- @EnableTransactionManagement:开启注解式事务的支持;
- @EnableCaching:开启注解式的缓存支持;
3.13、测试相关注解
@RunWith
运行器,Spring中通常用于对JUnit的支持
@ContextConfiguration
用来加载配置配置文件,其中classes属性用来加载配置类。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:/*.xml"})
public class CDPlayerTest {
}
@ContextConfiguration这个注解通常与@RunWith(SpringJUnit4ClassRunner.class)联合使用用来测试。
@ContextConfiguration括号里的locations = {“classpath*😕*.xml”}就表示将classpath路径里所有的xml文件都包括进来,自动扫描的bean就可以拿到,此时就可以在测试类中使用@Autowired注解来获取之前自动扫描包下的所有bean。
3.14、@SuppressWarnings
Suppress 抑制;镇压;废止 Warnings警告
**@SuppressWarnings(“resource”)**是J2SE 提供的一个批注。该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。
@SuppressWarnings 批注允许您选择性地取消特定代码段(即,类或方法)中的警告。其中的想法是当您看到警告时,您将调查它,如果您确定它不是问题,您就可以添加一个 @SuppressWarnings 批注,以使您不会再看到警告。
虽然它听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止您对警告无动于衷 — 您看到的每一个警告都将值得注意。
4、SpringMVC常用注解
4.1、@EnableWebMvc
在配置类中开启Web MVC的配置支持。
4.2、@Controller
4.3、@RequestMapping
用于映射web请求,包括访问路径和参数。
4.4、@ResponseBody
支持将返回值放到response内,而不是一个页面,通常用户返回json数据。
4.5、@RequestBody
允许request的参数在request体中,而不是在直接连接的地址后面。(放在参数前)
4.6、@PathVariable
用于接收路径参数,比如@RequestMapping(“/hello/{name}”)声明的路径,将注解放在参数前,即可获取该值,通常作为Restful的接口实现方法。
4.7、@RestController
该注解为一个组合注解,相当于**@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody**。
4.8、@ControllerAdvice
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
- ControllerAdvice的常用场景
4.9、@ExceptionHandler
用于全局处理控制器里的异常。
4.10、@InitBinder
用来设置WebDataBinder,WebDataBinder用来自动绑定前台请求参数到Model中。
4.11、@ModelAttribute
(1)@ModelAttribute注释方法
如果把**@ModelAttribute放在方法的注解上时,代表的是:该Controller的所有方法在调用前,先执行此@ModelAttribute方法。可以把这个@ModelAttribute特性,应用在BaseController当中,所有的Controller继承BaseController**,即可实现在调用Controller时,先执行**@ModelAttribute**方法。比如权限的验证(也可以使用Interceptor)等。
(2)@ModelAttribute注释一个方法的参数
当作为方法的参数使用,指示的参数应该从模型中检索。如果不存在,它应该首先实例化,然后添加到模型中,一旦出现在模型中,参数字段应该从具有匹配名称的所有请求参数中填充。
private static String s;
@ModelAttribute
public String getString(){
s = "123";
return s;
}
@PostMapping("/test")
@ApiOperation(value="ModelAttribute注解测试接口",notes="ModelAttribute注解测试接口后端接口",response = String.class)
public String helloWorld(){
System.out.println("s = " + s);
return s;
}
4.12、@Transactional
@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。
EmployeeService 的所有方法都支持事务并且是只读。当类级别配置了**@Transactional**,方法级别也配置了**@Transactional**,应用程序会以方法级别的事务属性信息来管理事务,换言之,方法级别的事务属性信息会覆盖类级别的相关配置信息。
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)
@Service(value ="employeeService")
public class EmployeeService
4.12.1 @Transactional 注解的属性信息
属性名 | 说明 |
---|---|
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | 事务的隔离度,默认值采用 DEFAULT。 |
timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
5、循环依赖
5.1 本质
static class Jxj{
private Zs zs = new Zs();
}
static class Zs{
private Jxj jxj = new Jxj();
}
public static void main(String[] args) {
Jxj jxj = new Jxj();
}
互相循环依赖会导致栈溢出,无法解决。
Exception in thread "main" java.lang.StackOverflowError
解决思路:
- 先创建A,但是还没有完成初始化操作,也就是一个半成品对象,
- 然后在赋值的时候先把A暴露出来,然后创建B,
- 让B完成创建后找到暴露的A完成整体的实例化,这时再把B交给A完成A的后续操作。
- 通过构造器创建对象
- 完成对象的实例化
//保存提前暴露的对象,也就是半成品对象
private final static Map<String,Object> singletonObject = new ConcurrentHashMap<>();
static class Jxj{
private Zs zs;
public Zs getZs() {
return zs;
}
public void setZs(Zs zs) {
this.zs = zs;
}
}
static class Zs{
private Jxj jxj;
public Jxj getJxj() {
return jxj;
}
public void setJxj(Jxj jxj) {
this.jxj = jxj;
}
}
public static void main(String[] args) throws Exception{
System.out.println(getBean(Jxj.class).getZs());
System.out.println(getBean(Zs.class).getJxj());
}
private static <T> T getBean(Class<T> beanClass)throws Exception{
//1.获取类对象对应的名称
String beanName = beanClass.getSimpleName().toLowerCase();
//2.根据名称去singletonObject 中查找是否有有半成品对象
if (singletonObject.containsKey(beanName)){
return (T) singletonObject.get(beanName);
}
//3.singletonObject中没有半成品对象,则反射实例化对象
Object o = beanClass.newInstance();
//还没有完整的创建完这个对象就把它存储在singletonObject中
singletonObject.put(beanName,o);
//属性填充来补全对象
Field [] fields = o.getClass().getDeclaredFields();
//遍历处理
for (Field field : fields) {
//针对private修饰
field.setAccessible(true);
//获取成员变量 对应的类对象
Class<?> fieldClass = field.getType();
//获取对用的 beanName
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
//给成员变量赋值,如果 singletonObject 中有半成品就获取,否则创建对象
field.set(o,singletonObject.containsKey(fieldBeanName)?
singletonObject.get(fieldBeanName):getBean(fieldClass));
}
return (T) o;
}
5.2 Spring中是如何解决循环依赖的?
-
构造注入:无解
-
设值注入:提前暴露
5.3 Spring中构造注入的循环依赖问题
- 在创建Bean的时候记录下这个Bean,创建完成后,我们再移除这个Bean,
- 然后我们在getBean的时候判断记录中是否有Bean,
- 如果有我们就判断为循环依赖,并抛出异常,数据结构我们可以通过Set集合来处理。
6、Spring Bean的生命周期
简单来说,Spring Bean的生命周期只有四个阶段:实例化 Instantiation --> 属性赋值 Populate --> 初始化 Initialization --> 销毁 Destruction
但具体来说,Spring Bean的生命周期包含下图的流程:
(1)实例化Bean:
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
(2)设置对象属性(依赖注入):
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入。
(3)处理Aware接口:
Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:
①如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字;
②如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
②如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
③如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor前置处理:
如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean:
如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。
(6)init-method:
如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
(7)BeanPostProcessor后置处理:
如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
(8)DisposableBean:
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
(9)destroy-method:
最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
7、Spring支持几种作用域?
- prototype:为每一个bean请求提供一个实例。
- singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
- request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
- session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
- global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。
8、Spring事务的隔离级别
事务隔离级别指的是一个事务对数据的修改与另一个并行的事务的隔离程度,当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:
8.1 Spring支持的隔离级别
再必须强调一遍,不是事务隔离级别设置得越高越好,
事务隔离级别设置得越高,意味着势必要花手段去加锁用以保证事务的正确性,那么效率就要降低,
实际开发中往往要在效率和并发正确性之间做一个取舍,一般情况下会设置为READ_COMMITED(读已提交),
此时避免了脏读,并发性也还不错,之后再通过一些别的手段去解决不可重复读和幻读的问题就好了。
9、Spring中的事务传播属性
保证事务:ACID
针对的是嵌套的关系
Spring中的7个事务传播行为
public static class ServiceA{
static void methodA(){
ServiceB.methodB();
}
}
public static class ServiceB{
static void methodB(){
}
}
9.1 PROPAGATION_REQUIRED
- 假如当前正要运行的事务不在另外一个事务里,那么就起一个新的事务比方说,
- ServiceB.methodB的事务级别定义PROPAGATION_REQUIRED,
- 那么因为执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务。
- 这时调用ServiceB.methodB,ServiceB.methodB看到自己已经执行在ServiceA.methodA的事务内部。
- 就不再起新的事务。
- 而假如ServiceA.methodA执行的时候发现自己没有在事务中,他就会为自己分配一个事务。
- 这样,在ServiceA.methodA或者在ServiceB.methodB内的不论什么地方出现异常。事务都会被回滚。
- 即使ServiceB.methodB的事务已经被提交,可是ServiceA.methodA在接下来fail要回滚,
- ServiceB.methodB也要回滚
9.2.PROPAGATION_sUPPORTS
假设当前在事务中。即以事务的形式执行。假设当前不在一个事务中,那么就以非事务的形式执行
9.3PROPAGATION_MANDATORY
必须在一个事务中执行。也就是说,他仅仅能被一个父事务调用。否则,他就要抛出异常
9.4.PROPAGATION REQUIRES NEW
- 如果我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,
ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW。 - 那么当运行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起。
- ServiceB.methodB会起一个新的事务。等待
ServiceB.methodB的事务完毕以后,他才继续运行。
他与PROPAGATION_REQUIRED的事务差别在于事务的回滚程度了。 - 由于ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。
- 假设ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚。
- ServiceB.methodB是不会回滚的。假设ServiceB.methodB失败回滚,假设他抛出的异常被
ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
10、Spring中事务实现方式
编程式事务管理:
通过编程的方式管理,灵活性大,难维护。
声明式事务管理:
将事务管理和业务代码分离,只需要通过注解或XML配置管理事务。
11、 事务的本质
- Transactional 这个注解仅仅是一些(和事务相关的)元数据,在运行时被事务基础设施读取消费,并使用这些元数据来配置bean的事务行为。大致来说具有两方面功能,一是表明该方法要参与事务,二是配置相关属性来定制事务的参与方式和运行行为
- 声明式事务主要是得益于Spring AOP。使用一个事务拦截器,在方法调用的前后/周围进行事务性增强(advice),来驱动事务完成。
- @Transactional注解既可以标注在类上,也可以标注在方法上。当在类上时,默认应用到类里的所有方法。如果此时方法上也标注了,则方法上的优先级高。另外注意方法一定要是public的。
11.1 事务JDBC原生实现方式
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
//注册JDBC驱动
//打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zyw?serverTimezone=UTC&characterEncoding=UTF-8","root","root");
//执行查询
stmt = conn.createStatement();
//关闭自动提交
conn.setAutoCommit(false);
//添加操作
String sql = "INSERT INTO user (name,age,phone) VALUES ('江先进',24,'15002711507')";
stmt.executeUpdate(sql);
//添加日志
sql = "INSERT INTO user_log (name,log) VALUES ('江先进','添加了用户')";
stmt.executeUpdate(sql);
//两个操作都没有问题则提交
conn.commit();
}catch (Exception e){
e.printStackTrace();
//出现问题就回滚
try {
conn.rollback();
}catch (SQLException throwable){
throwable.printStackTrace();
}
}finally {
//资源回收
try {
if (stmt!=null){
stmt.close();
}
}catch (SQLException se1){
}
try {
if (conn!=null) {
conn.close();
}
}catch (SQLException se2){
se2.printStackTrace();
}
}
}
11.2 编程式事务
@Autowired
private UserDao userDao;
@Autowired
private UserLogDao userLogDao;
@Autowired
private PlatformTransactionManager txManager;
/**
* 编程式事务
* 通过调用事务源码提供的API完成事务相关功能
* @param user
*/
@Transactional
public void insertUserByApi(User user){
//1、创建事务定义
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
//2、根据定义开启事务
TransactionStatus status = txManager.getTransaction(definition);
try {
this.userDao.insert(user);
UserLog log = UserLog.builder().id("1").name("程先硕").log("添加了用户" + user.getName()).build();
this.userLogDao.insert(log);
//提交事务
txManager.commit(status);
}catch (Exception e){
//异常了,回滚事务
txManager.rollback(status);
throw e;
}
}
11.3 声明式事务
大多数Spring框架的用户选择声明式事务管理,因为声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理少了一点灵活性。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
11.4 Spring事务回滚
如果捕获了异常,还想让事务生效,示例:
@Transactional
publicvoidcreateUserRight1(String name){
try{
userRepository.save(newUserEntity(name));
thrownewRuntimeException("error");
}catch(Exception ex){
log.error("create user failed", ex);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
Spring Boot默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。比如代码中抛出RuntimeException 就没有问题,但是抛出SQLException就无法回滚。针对非运行时异常,如果要进行事务回滚的话,可以在@Transactional注解中使用rollbackFor 属性来指定异常,比如 @Transactional(rollbackFor =Exception.class),这样就没有问题了,所以在实际项目中,一定要指定异常。
12、@Transactional的使用
12.1 常用参数
12.1.1 传播行为参数
- @Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下);
- @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
- @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
- @Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
- @Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
- @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
12.1.2 超时参数
- @Transactional(timeout=30) ,超时时间默认是30秒。
12.1.3 隔离级别参数
- @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用;
- @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读);
- @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读);
- @Transactional(isolation = Isolation.SERIALIZABLE):串行化。
- MYSQL: 默认为REPEATABLE_READ级别。
- SQLSERVER: 默认为READ_COMMITTED。
12.1.4 常用参数汇总
参数名称 | 功能描述 |
---|---|
readOnly | 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
rollbackFor | 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如: 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName | 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如: 指定单一异常类名称:@Transactional(rollbackForClassName=“RuntimeException”) 指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”}) |
noRollbackFor | 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如: 指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如: 指定单一异常类名称:@Transactional(noRollbackForClassName=“RuntimeException”) 指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,“Exception”}) |
propagation | 该属性用于设置事务的传播行为,例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation | 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
timeout | 该属性用于设置事务的超时秒数,默认值为-1表示永不超时 |
12.1.5 事务的嵌套失效
嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于save point。
如果子事务回滚,父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。
如果父事务回滚,父事务回滚,子事务也会跟着回滚。为什么呢,因为父事务结束之前,子事务是不会提交的。
事务的提交:子事务是父事务的一部分,由父事务统一提交。
13、SpringMVC的理解
控制框架:前端控制器–》Servlet–》Web容器【Tomcat】
SpringMVC和Spring的关系:IOC容器关系–》父子关系
14、SpringBoot的理解
14.1 自动装配
SpringBoot的初始化–> loC Spring的初始化
@SpringApplication注解–>@Configuration -->ConfigurationClassPostProcessor --》@lmport注解–》延迟加载–》自动装配–> SPI去重排除过滤
启动类:
@SpringBootApplication
public class ShApplication {
public static void main(String[] args) {
// SpringBoot项目的启动。
//完成的是一个Spring容器的初始化过程
SpringApplication.run(ShApplication.class, args);
}
}
@SpringBootApplication注解:
@Target(ElementType.TYPE) //目标:定义被修饰的注解所能修饰的位置,方法上、成员变量上
@Retention(RetentionPolicy.RUNTIME)//生命周期
@Documented//文档抽取
@Inherited//嵌套
@SpringBootConfiguration//声明此为Java配置类
@EnableAutoConfiguration
@ComponentScan(//定义扫描路径,若无指定默认扫描启动类所在的包和子包
excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
·
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@EnableAutoConfiguration:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)//1.导入Java类到容器中;
//2.如果导入的类实现了ImportSelector接口,则会调用其中的selectImports方法
//3.如果导入的类实现了ImportBeanDefinitionRegistrar接口,则会调用其中的registerBeanDefinitions方法
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
加载配置文件到内存中:AutoConfigurationImportSelector.getAutoConfigurationEntry()方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//加载META-INF/spring.factories配置文件到内存中
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//去除名称重复的
configurations = removeDuplicates(configurations);
//定义需要排除的文件(不需要使用到的),通过filter过滤掉
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
getCandidateConfigurations()
15、@Import注解的理解
Spring3.0时提供,目的是为了替换在XML配置文件中的标签
@Import除了可以导入第三方的Java配置类,还拓展了其他的功能
- 1.可以把某个类型的对象注入到容器中
- 2.如果导入的类实现了ImportSelector接口,则会调用其中的selectImports方法,把方法返回的类型全类路径的类型对象注入到容器中。
- 3.如果导入的类实现了ImportBeanDefinitionRegistrar接口,则会调用声明的方法在该方法中显示的提供注册器来完成注入(registerBeanDefinitions方法)。
16、SpringBoot自动装配中为什么用DeferredImportSelector
本质作用:延迟注入Bean实例
- 在SpringBoot自动装配中核心是会加载所有依赖中的META-INF/spring.factories文件中的配置信息。
- 我们可能有多个需要加载的spring.factories文件。那么我们就需要多次操作。
- 我们可以考虑把所有的信息都加载后再统—把这些需要注入到容器中的内容注入进去
17、Bootstrap.yml的作用
使用场景:正常启动时需要做一些前置的操作。
- 在单体的SpringBoot项目中其实我们是用不到bootstrap.yml文件的,bootsrap.yml文件的使用需要SpringCloud的支持,因为在微服务环境下我们都是有配置中心的,来统一的管理系统的相关配置属性,那么怎么去加载配置中心的内容呢?
- 一个SpringBoot项目启动的时候默认只会加载对应的application.yml中的相关信息,
- 这时bootstrap.yml的作用就体现出来了,会在SpringBoot正常启动前创建一个父容器来通过
bootstrap.yml中的配置来加载配置中心的内容。
18、如何对属性文件中的账号密码加密?
- 我们在application.yml中保存的MySQL数据库的账号密码或者其他服务的账号密码,都可以保存加密后的内容,
- 只需要对SpringBoot的执行流程清楚就可以了,
- 第一个我们可以通过自定义监听器可以在加载解析了配置文件之后对加密的文件中做解密处理同时覆盖之前加密的内容,或者通过对应的后置处理器来处理,具体的实现如下:
Run方法:
在SpringBoot项目启动的时候在在刷新Spring容器之前执行的,所以我们要做的就是在加载完环境配置信息后,获取到配置的spring.datasource.password=t53d2CzFMEw=这个信息,然后解密并修改覆盖就可以了。
然后在属性文件的逻辑其实是通过发布事件触发对应的监听器来实现的
所以第一个解决方案就是你自定义一个监听器,这个监听器在加载属性文件(ConfigFileApplicationListener)的监听器之后处理,这种方式稍微麻烦点,
还有一种方式就是通过加载属性文件的一个后置处理器来处理,这就以个为例来实现
后置处理器:实现EnvironmentPostProcessor接口
19、@Indexed注解的理解
@Indexed在Spring5.0提供
解决的问题:随着项目越来越复杂那么@ComponentScan需要扫描加载的Class会越来越多。在系统启动的时候会造成性能损耗。所以Indexed注解的作用其实就是提升系统启动的性能。(编译时做了前置处理)
- 在系统编译的时候那么会收集所有被@Indexed注解标识的Java类。
- 然后记录在META-INF/spring.components文件中。
- 那么系统启动的时候就只需要读取一个该文件中的内容就不用在遍历所有的目录了。提升的效率
20、@Component、@Controller、@Repository、@Service
目标:作为标签,便于检索、管理和使用。
@Component:这将java类标记为bean。它是任何Spring管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
@Controller : 这将一个类标记为Spring Web MVC 控制器。标有它的Bean 会自动导入到 loC容器中。
@Service :此注解是组件注解的特化。它不会对@Component 注解提供任何其他行为。您可以在服务层类中使用@Service而不是@Component,因为它以更好的方式指定了意图。
@Repository :这个注解是具有类似用途和功能的@Component 注解的特化。它为DAO提供了额外的好处。它将DAO导入loC容器,并使未经检查的异常有资格转换为Spring DataAccessException,
21、Spring中的AOP有哪些通知类型(Advice)?
21.1 Advice的类型
前置通知:Before -这些类型的Advice在joinpoint方法之前执行,并使用@Before注解标记进行配置。
后置通知:After Returning -这些类型的Advice 在连接点方法正常执行后执行,并使用@AfterReturning 注解标记进行配置。
异常通知:After Throwing -这些类型的Advice仅在joinpoint方法通过抛出异常退出并使用@AfterThrowing注解标记配置时执行。
最终通知:After (finally)-这些类型的Advice在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用@After注解标记进行配置。
环绕通知:Around -这些类型的Advice在连接点之前和之后执行,并使用@Around注解标记进行配置。
21.2、Advice的执行顺序:
(1)没有异常情况下的执行顺序:
-
around before advice
-
before advice
-
target method 执行
-
after advice
-
around after advice
-
afterReturning advice
(2)出现异常情况下的执行顺序:
-
around before advice
-
before advice
-
target method
-
执行after advice
-
around after advice
-
afterThrowing advice
-
java.lang.RuntimeException:异常发生
22、Spring的依赖注入
- 依赖注入,是IOC的一个方面,是个通常的概念,它有多种解释。
- 这概念是说你不用创建对象,而只需要描述它如何被创建。
- 你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器((IOC容器)负责把他们组装起来。
23、Spring的IOC理解
23.1 什么是IOC:
IOC,Inversion of Control,控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。
最直观的表达就是,以前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如Connection等),对象始终会和其他接口或类耦合起来。而 IOC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new 了,只需告诉 Spring 容器,然后 Spring 就会在系统运行到适当的时机,把你想要的对象主动给你。也就是说,对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转。
23.2 什么是DI:
IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性。
23.3 IoC的原理:
Spring 的 IoC 的实现原理就是工厂模式加反射机制,而在 Spring 容器中,Bean 对象如何注册到 IoC 容器,以及Bean对象的加载、实例化、初始化详细过程可以阅读这篇文章:Spring的Bean加载流程_张维鹏的博客-CSDN博客
24、Spring的AOP理解:
OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
24.1 静态代理:
AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
24.2 动态代理:
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
① JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理对象; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
② 如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
24.3 区别:
静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
25、Spring AOP里面的几个名词的概念:
(1)连接点(Join point):
指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。
(2)切面(Aspect):
被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。
在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。
(3)切点(Pointcut):
切点用于定义 要对哪些Join point进行拦截。
切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。
(4)通知(Advice):
指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。
(5)目标对象(Target):
包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。
(6)织入(Weaving):
通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。
(7)引入(Introduction):
添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
26、Spring容器的启动流程:
26.1 初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中:
① 实例化BeanFactory【DefaultListableBeanFactory】工厂,用于生成Bean对象
② 实例化BeanDefinitionReader注解配置读取器,用于对特定注解(如@Service、@Repository)的类进行读取转化成 BeanDefinition 对象,(BeanDefinition 是 Spring 中极其重要的一个概念,它存储了 bean 对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等)
③ 实例化ClassPathBeanDefinitionScanner路径扫描器,用于对指定的包目录进行扫描查找 bean 对象
26.2 将配置类的BeanDefinition注册到容器中:
26.3 调用refresh()方法刷新容器:
- ① prepareRefresh()刷新前的预处理:
- ② obtainFreshBeanFactory():获取在容器初始化时创建的BeanFactory:
- ③ prepareBeanFactory(beanFactory):BeanFactory的预处理工作,向容器中添加一些组件:
- ④ postProcessBeanFactory(beanFactory):子类重写该方法,可以实现在BeanFactory创建并预处理完成以后做进一步的设置
- ⑤ invokeBeanFactoryPostProcessors(beanFactory):在BeanFactory标准初始化之后执行BeanFactoryPostProcessor的方法,即BeanFactory的后置处理器:
- ⑥ registerBeanPostProcessors(beanFactory):向容器中注册Bean的后置处理器BeanPostProcessor,它的主要作用是干预Spring初始化bean的流程,从而完成代理、自动注入、循环依赖等功能
- ⑦ initMessageSource():初始化MessageSource组件,主要用于做国际化功能,消息绑定与消息解析:
- ⑧ initApplicationEventMulticaster():初始化事件派发器,在注册监听器时会用到:
- ⑨ onRefresh():留给子容器、子类重写这个方法,在容器刷新的时候可以自定义逻辑
- ⑩ registerListeners():注册监听器:将容器中所有的ApplicationListener注册到事件派发器中,并派发之前步骤产生的事件:
- ⑪ finishBeanFactoryInitialization(beanFactory):初始化所有剩下的单实例bean,核心方法是preInstantiateSingletons(),会调用getBean()方法创建对象;
- ⑫ finishRefresh():发布BeanFactory容器刷新完成事件。
27、Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?
Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。
(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。
(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。
无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。
也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
28、Spring 框架中都用到了哪些设计模式?
Spring设计模式的详细使用案例可以阅读这篇文章:Spring中所使用的设计模式_张维鹏的博客-CSDN博客_spring使用的设计模式
(1)工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象
(2)单例模式:Bean默认为单例模式
(3)策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
(4)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
(5)模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate
(6)适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller
(7)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。
(8)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
29、注解的原理:
(1)什么是注解:
Java 注解就是代码中的一些特殊标记(元信息),用于在编译、类加载、运行时进行解析和使用,并执行相应的处理。它本质是继承了 Annotation 的特殊接口,其具体实现类是 JDK 动态代理生成的代理类,通过反射获取注解时,返回的也是 Java 运行时生成的动态代理对象 $Proxy1。通过代理对象调用自定义注解的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法,该方法会从 memberValues 这个Map中查询出对应的值,而 memberValues 的来源是Java常量池。
注解在实际开发中非常常见,比如 Java 原生的 @Overried、@Deprecated 等,Spring的 @Controller、@Service等,Lombok 工具类也有大量的注解,不过在原生 Java 中,还提供了元 Annotation(元注解),他主要是用来修饰注解的,比如 @Target、@Retention、@Document、@Inherited 等。
- @Target:标识注解可以修饰哪些地方,比如方法、成员变量、包等,具体取值有以下几种:ElementType.TYPE/FIELD/METHOD/PARAMETER/CONSTRUCTOR/LOCAL_VARIABLE/ANNOTATION_TYPE/PACKAGE/TYPE_PARAMETER/TYPE_USE
- @Retention:什么时候使用注解:SOURCE(编译阶段就丢弃) / CLASS(类加载时丢弃) / RUNTIME(始终不会丢弃),一般来说,我们自定义的注解都是 RUNTIME 级别的,因为大多数情况我们是根据运行时环境去做一些处理,一般需要配合反射来使用,因为反射是 Java 获取运行是的信息的重要手段
- @Document:注解是否会包含在 javadoc 中;
- @Inherited:定义该注解与子类的关系,子类是否能使用。
(2)如何自定义注解?
① 创建一个自定义注解:与创建接口类似,但自定义注解需要使用 @interface
② 添加元注解信息,比如 @Target、@Retention、@Document、@Inherited 等
③ 创建注解方法,但注解方法不能带有参数
④ 注解方法返回值为基本类型、String、Enums、Annotation 或其数组
⑤ 注解可以有默认值;
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface CarName {
String value() default "";
}