1.Spring IOC(控制反转)和AOP(面相切面编程)的理解
控制反转意味着将对象的控制权从代码中转移到Spring IOC容器。
本来是我们自己手动new出来的对象,现在则把对象交给Spring的IOC容器管理,IOC容器作为一个对象工厂,管理对象的创建和依赖关系。
控制反转是一种思想,依赖注入是控制反转的实现方式,通过IOC容器自动将依赖关系注入到需要它们的对象中。
Spring IOC的好处:
- 集中统一管理对象,降低耦合度。
- 便于单元测试、处理复杂对象创建和依赖关系、实现单例等。
- Spring提供了完整的Bean生命周期管理,包括对象增强(如AOP)。
所谓的「面向切面编程」在我理解下其实就是在方法前后增加非
业务代码AOP底层的技术是动态代理,在Spring内实现依赖的是BeanPostProcessor
比如我们需要在方法上注入些「重复性」的非业务代码,就可以利用Spring AOP
Spring AOP 解决的是 非业务代码抽取的问题
你们项目一般是怎么把对象交给IOC容器管理的?(换个问法:一般是怎么定义Bean的?)
Spring提供了4种方式,分别
是
1):注解
2):XML
3):JavaConfig
4):基于GroovyDSL配置
一般项目我们用注解或XML比较多,少部分用JavaConfig
日常写业务代码一般用注解来定义各种对象,责任链这种一般配
置在XML,「注解」解决不了的就用JavaConfig
2.Spring、SpringMVC、SpringBoot有什么区别?
pring+spring MVC+mybatis=传统ssm项目,spring负责本地的bean管理和事务处理,spring MVC负责对接web。spring boot则是综合了这俩,延展出来的新生态企业应用级快速开发架构,使各位cv工程师专注于敲代码,而不是去搞传统ssm那套繁琐配置。
因此,SpringBoot可以看作是在 Spring 的基础上,通过自动配置和约定优于配置的方式,提供了更加简单、快速的开发体验。而 SpringMVC 则是 Spring 框架中用于构建 Web 应用程序的模块。
3.SpringBoot实现自动化配置的原理
自动配置是通过@EnableAutoConfiguration注解实现的。这个注解会通过@import注解导入
AtuoConfigurationlmportSelector类,这个类会扫描META-INF/Spring.factories文件,这个文件里面定义了所有的自动配置类,这些配置类上可能存在条件注解,spring boot启动的时候根据这些注解,加载这些自动配置类。
1.自定义自动配置类步骤
定义一个类使用Configuration注解修饰;
定义META-INF/Spring-factories文件,向EnableAutoConfiguration中注入这个类
4.Spring 框架中都用到了哪些设计模式?
Spring 框架中使用了许多设计模式,以下列举一些比较重要的:
- 单例模式:Spring 的 Bean 默认是单例模式,通过 Spring 容器管理 Bean 的生命周期,保证每个 Bean 只被创建一次,并在整个应用程序中重用。
- 工厂模式:Spring 使用工厂模式通过 BeanFactory 和 ApplicationContext 创建并管理 Bean 对象。
- 代理模式:Spring AOP 基于动态代理技术,使用代理模式实现切面编程,提供了对 AOP 编程的支持
- 观察者模式:Spring 中的事件机制基于观察者模式,通过 ApplicationEventPublisher 发布事件,由
ApplicationListener 监听事件,实现了对象间的松耦合。 - 模板方法模式:Spring 中的 JdbcTemplate 使用了模板方法模式,将一些固定的流程封装在父类中,子类只需实现一些抽象方法即可。
- 策略模式:Spring 中的 HandlerInterceptor 和 HandlerExecutionChain 使用了策略模式,允许开发者自定义处理器拦截器,按照一定顺序执行。
- 责任链模式:Spring 中的过滤器和拦截器使用了责任链模式,多个过滤器和拦截器按照一定顺序执行,每个过滤器和拦截器可以拦截请求或者响应并做出相应的处理。
5.为什么SpringBoot使用CGLIB作为默认AOP动态代理?
动态代理可以代理任何类型的目标类,无论它是否实现了接口;
JDK 动态代理只能代理实现了接口的目标类;
CGLIB 动态代理可以覆盖 JDK 动态代理的所有场景,而 JDK 动态代理不能覆盖 CGLIB 动态代理的所有场景;
自动注入时,JDK动态代理只能用接口IUserService
接收,而CGLIB两种都可以。
6.SpringBoot启动原理
- 启动类与注解解析。Spring Boot 应用通常由一个带有 @SpringBootApplication 注解的主启动类启动。@SpringBootApplication 是一个复合注解,主要由以下三个注解构成。
装饰器模式:@SpringBootApplication 通过组合多个注解来扩展功能,类似装饰器模式,为启动类添加了多个行为。 - SpringApplication 的构建与服务类型确定。SpringApplication.run() 是 Spring Boot 应用启动的入口。SpringApplication 是核心服务对象,构建过程包括:资源加载器与主方法类的记录。
- 环境准备。在 run() 方法中,环境准备阶段的目标是为 Spring 应用上下文提供配置和上下文信息:创建并配置 ConfigurableEnvironment 环境对象。
- 容器创建。容器创建是 Spring Boot 启动过程的核心环节,它涉及到 Spring 应用上下文的初始化和配置。
- 通过 createApplicationContext 创建合适的 ApplicationContext 实例.
- 加载并注册核心 Bean 工厂、配置类处理器、自动装配处理器等组件。
- 填充容器。容器创建后,Spring Boot会自动装配应用中定义的所有 Bean,过程包括:
- 加载所有 Bean 定义并实例化。
- 执行 Bean 的生命周期回调方法,如 @PostConstruct、@PreDestroy。
- 启动内嵌的 Web 服务器(如 Tomcat、Jetty)。
7.SpringBoot配置文件加载顺序(优先级)?
优先级从高到低,高优先级的配置覆盖低优先级的配置,所有配置会形成互补配置。
1.命令行参数。所有的配置都可以在命令行上进行指定;
2.Java系统属性(System.getProperties0));
3.操作系统环境变量;
4.jar包,外部的application-{profile}.properties
或application.yml(带spring.profile)配置文件
5.jar包内部的application-{profile).properties
或application.yml(带spring.profile)配置文件 再来加载不带profile
6.jar包外部的application.properties
或application.yml(不带spring.profile)配置文件
7.jar包内部的application.properties
或application.yml(不带spring.profile)配置文件
8.@Configuration注解类上的@PropertySource
8.SpringCloud是什么?
Spring Cloud是一系列框架的集合,使用这些组件实现微服务。它利用Spring Boot的开发便利性简化了分布式系统基础设施的开发,如服务发现、服务注册、配置中心、消息总线、负载均衡、熔断器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud必须依赖Spring Boot开发,单独Spring Boot可以单独开发。
SpringCloud的几种常用组件如下:
1.服务发现–Netflix Eureka (Nacos)
Nacos用于实现注册中心,更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它支持多种服务治理能力,包括服务注册与发现、动态配置管理、动态DNS服务等。
2.服务调用–Netflix Feign
Feign是一个声明式的Web服务客户端,用来简化HTTP远程调用。在Spring Cloud中,Feign可以用来封装HTTP调用的接口。
Feign与Ribbon结合使用可以实现客户端负载均衡。Feign本身不直接支持负载均衡策略,而是通过集成Ribbon来实现。因此,Feign可以使用Ribbon支持的各种负载均衡策略,包括轮询、随机、权重、最佳可用等。
3.熔断器–Netflix Hystrix (Sentinel)
4.服务网关–Spring Cloud GakteWay
5.分布式配置–Spring Cloud Config (Nacos)
6.负载均衡–Ribbon
ribbon用于实现负载均衡,发起远程调用feign。
Ribbon负载均衡策略有:
- RoundRobinRule:简单轮询服务列表来选择服务器
- WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小
- RandomRule:随机选择一个可用的服务器
9.SpringCloud微服务是什么?
微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现。
微服务,关键其实不仅仅是微服务本身,而是系统要提供一套基础的架构,这种架构使得微服务可以独立的部署、运行、升级,不仅如此,这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在功能上则表现为一个统一的整体。这种所谓的“统一的整体"表现出来的是统一风格的界面,统一的权限管理,统一的安全策略,统一的上线过程,统一的日志和审计方法,统一的调度方式,统一的访问入口等等
10.Spring中写一个接口最后返回json对象该怎么实现?
- 使用SpringMVC时可以使用
@ResponseBody
注解,方便的返回json数据 ,该注解用于将Controller的方法返回的对象,根据HTTP Request Header的中的Accept属性,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。 - 使用
@RestController
注解Controller,它的作用相当于@ResponseBody+@Controller,意思是Controller处理完请求后直接返回json格式化数据。
11.Springboot过滤器和拦截器的区别?
过滤器是拦截所有请求,而拦截器是拦截在进入到前端控制器之后的请求
Filter和Listener:依赖Servlet容器,基于函数回调实现。可以拦截所有请求,覆盖范围更广,但无法获取ioc容器中的bean。
Interceptor和aop:依赖spring框架,基于java反射和动态代理实现。只能拦截controller的请求,可以获取ioc容器中的bean,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
过滤器的应用场景:
Filter过滤器是Servlet容器层面的,在实现上基于函数回调,可以对几乎所有请求进行过滤。
- 在访问网站时,有时候会发布一些敏感信息,发完以后有的会用*替代。
- 还有就是登陆权限控制等,一个资源,没有经过授权,肯定是不能让用户随便访问的,
拦截器应用场景:
拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括: - 登录验证,判断用户是否登录。
- 权限验证,判断用户是否有权限访问资源,如校验token
- 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量。
拦截器的拦截是基于反射实现的。
12.SpringCloud中的Nacos是AP还是CP?
CAP原则:cap理论是针对分布式数据库而言的,它是指在一个分布式系统中,一致性(Consistency,C)、可用性(Availability,A)、分区容错性(Partition Tolerance, P)三者不可兼得。
AP模式(Availability Priority Mode)高可用性
CP模式(Consistency Priority Mode)一致性
Nacos支持AP(可用性 | 分区容错性) 和 CP(一致性 | 分区容错性)两种 默认是AP。
nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;eureka采用AP方式。
如果注册Nacos的client节点注册时ephemeral=true,那么Nacos集群对这个client节点的效果就是AP,采用distro协议实现;而注册Nacos的client节点注册时ephemeral=false,那么Nacos集群对这个节点的效果就是CP的,采用raft协议实现。根据client注册时的属性,AP,CP同时混合存在,只是对不同的client节点效果不同。Nacos可以很好的解决不同场景的业务需求。
13.Spring AOP是什么?
Spring的AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的一个重要特性,用于将横切关注点从应用程序的主业务逻辑中分离出来,使得关注点的代码可以被模块化、重用,并且与主业务逻辑解耦。
1)切面(Aspect)
:其实就是定义了一个java类,里面包含了通知(advice)和切点(pointcut),定义了在何处
以及何时执行通知,将切面的一些东西模块化了,即定义横切关注点的模块,封装了不同模块共享的功能。
2)通知(Advice):切面中的实际逻辑,即在连接点上执行的操作。通知可以在方法执行前、执行后或抛出异常时
运行。
- 前置通知(Before advice):在目标方法执行前执行。
- 后置通知(Afterreturning advice):在目标方法成功执行后执行。
- 后置异常通知(After throwing advice):在目标方法抛出异常后执行。
- 后置最终通知(After (finally) advice):无论目标方法如何结束(正常返回或抛出异常),都会执行。
- 环绕通知(Around advice):在目标方法执行前后都执行,并且可以控制目标方法的执行过程,环绕通知可以
用作日志打印或者权限校验。
3)切点(Pointcut):切点是一个表达式,用于定义在哪些连接点上执行通知,简单理解就是通过这个表达式可以
找到想要织入的哪些方法。
4)连接点(Join point):连接点是程序执行过程中可以应用切面的点,例如方法的调用、方法的执行、异常的抛
出等,可以拿到切入方法名等诸多属性。
5)目标对象(Target Object):被切面增强的对象,也就是原本的业务类。
6)代理(Proxy):Spring AOP 通过生成代理对象来增强目标对象的方法。代理对象包含目标对象的原始方法和增
强逻辑。
7)织入(Weaving):将切面应用到目标对象上的过程。Spring AOP 是在运行时进行织入的。
实现切点的方式@Pointcut(“execution(* com.example.aopdemo.service..(…))”)
@Aspect
@Component
public class LoggingAspect {
// 定义切入点,匹配 service 包中的所有方法
@Pointcut("execution(* com.example.aopdemo.service.*.*(..))")
public void serviceMethods() {}
// 前置通知:在方法执行前记录日志
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
// 后置通知:在方法执行后记录日志
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}
// 返回通知:在方法成功返回结果后记录日志
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("Method returned with value: " + result);
}
// 异常通知:在方法抛出异常后记录异常信息
@AfterThrowing(pointcut = "serviceMethods()", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println("Method threw exception: " + error.getMessage());
}
// 环绕通知:在方法执行前后记录日志,且可以控制方法是否执行
@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before executing method: " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("After executing method: " + joinPoint.getSignature().getName());
return result;
}
}
advice 将按照以下的顺序
这里说下简单情况——针对一个方法只被一个aspect类拦截时,aspect类内部的 advice 将按照以下的顺序进行执行情况如下:
执行到核心业务方法或者类时,会先执行AOP。在aop的逻辑内,先走@Around注解的方法。然后是@Before注解的方法,然后这两个都通过了,走核心代码,核心代码走完,无论核心有没有返回值,都会走@After方法。然后如果程序无异常,正常返回就走@AfterReturn,有异常就走@AfterThrowing。
14.Spring AOP和AspectJ的区别?
Spring AOP:是 Spring 框架提供的一种 AOP 实现,主要用于运行时的代理机制。较轻量级,使用方便。
特点:Spring AOP 是基于动态代理实现的,适用于Spring 容器管理的 Bean,
使用场景:适合大部分业务场景,尤其是需要简单 AOP 功能的 Spring 应用.
AspectJ:AspectJ是功能更强大的 AOP 框架,支持编译时、类加载时和运行时的 AOP 功能。
特点:Aspect支持更加灵活的切点和增强操作,提供编译期和加载期的织入方式,性能较高
使用场景:适合对性能要求较高或需要复杂切点匹配的场景,如日志、监控等。
总的来说,Spring AOP 更适合于那些希望在现有的Spring 应用程序中轻松引入 AOP 的场景,而 Aspect 则
更适合于那些需要更强大、更灵活的 AOP 功能的应用场景。选择哪一个取决于具体的需求以及项目的技术
栈。
Aspect在编译时织入,静态代理,需要特殊编译器,功能更强大。而Spring aop是运行时织入的,基于动态
代理,因此不需要特殊编译器,功能限于增强方法。
15.Java Agent是什么?
面向切面编程(AOP)允许我们在目标方法的前后织入想要执行的逻辑
家介绍的Java Agent技术,在思想上与aop比较类似,翻译过来可以被称为Java代理、Java探针技术,
Java Agent出现在JDK1.5版本以后,它允许程序员利用agent技术构建一个独立于应用程序的代理程序,用途也非常广泛,可以协助监测、运行、甚至替换其他JVM上的程序,先从上图图直观的展示了其应用场景。
主要有两种模式:
- Premain模式允许在主程序执行前执行一个agent代理,启动之前在启动参数中添加
-javaagent:/jar包路径[=agentArgs]
- Agentmain模式可以说是premain的升级版本,它允许代理的目标主程序的jvm先行启动,再通过attach机制连接两个jvm
将Agent项目打包生成jar包,在META-INFO
中找到一个MANIFEST.MF
文件,在里面指定加载的是哪一个Agent:
Manifest-Version:1.0
Premain-Class: cn.bigfire.Agent1 //指定项目需要使用的Agent
Archiver-Version:Plexus Archiven
Built-By:xushu
Can-Retransform-Classes:true
Build-Jdk-spec:1.8
Created-By:Apache Maven 3.6.0
Build-Jdk:1.8.0 171
16.AOP环绕是否会导致业务代码异常,如何处理。
在Spring AOP中,环绕通知(@Around)确实有可能导致业务代码出现异常。这是因为环绕通知不仅可以在目标方法调用前执行代码,还可以在目标方法调用后执行代码,并且可以控制是否执行目标方法。如果环绕通知中的代码出现错误,可能会导致目标方法无法正确执行,甚至抛出异常。
如何处理环绕通知中的异常:
- 记录日志:在捕获异常后,可以记录详细的日志信息,以便后续排查问题。
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Around("execution(* com.example.service.MyService.targetMethod(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
try {
// 环绕通知前的操作
logger.info("Around advice before target method");
// 执行目标方法
Object result = joinPoint.proceed();
// 环绕通知后的操作
logger.info("Around advice after target method");
return result;
} catch (Exception e) {
// 记录异常日志
logger.error("Exception in around advice: ", e);
// 可以选择重新抛出异常,或者返回一个默认值
throw e; // 重新抛出异常
// 或者 return null; // 返回一个默认值
}
}
}
- 使用AOP提供的异常通知:Spring AOP提供了专门的异常通知注解 @AfterThrowing,可以在目标方法抛出异常后执行特定的逻辑。
@Aspect
@Component
public class ExceptionHandlingAspect {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlingAspect.class);
@AfterThrowing(pointcut = "execution(* com.example.service.MyService.targetMethod(..))", throwing = "ex")
public void handleException(Exception ex) {
// 处理异常
logger.error("Exception in target method: ", ex);
}
}
17.@Component和@Bean的区别?
- 用途不同:
@Component用于标识普通的类
@Bean是在配置类中声明利配置Bean对象 - 使用方式不同:
@Component是一个类级别的注解,Spring通过@ComponentScan注解扫描并注册为Bean
@Bean通过方法级别的注解使用,在配置类中手动声明一个Bean的定义 - 控制权不同:
@Component注解修饰的类是由Spring框架来创建和初始化的
@Bean注解允许开发人员手动控制Bean的创建和配置过程,更灵活一些
18.@Autowired和@Resource的区别?
-
@Autowired:自动注入,按照类型(type)自动装配,如果有多个同类型的Bean。可以用@Qualifier指定具体的Bean。
@Resource:JSR-250提供的Java自带的注入方式,按照名称(name)自动装配,也可以显示指定byType按类型注入。 -
@Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。
-
@Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。
@Component
public class QualifierTest {
//@Qualifier 和 @Autowired结合使用可以通过唯一Bean的id实现自动装配
@Autowired
@Qualifier("userA")
private User user;
public void test(){
System.out.println(user.getName());
}
}
19.Spring Bean的生命周期?
20.Spring中常用注解有哪些?以及对应的作用是什么?
1.@SpringBootApplication:
包含了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan。通常用于标注主类(启动类),以激活自动配置和组件扫描。
2.@RestController 和 @Controller:
@RestController 包含了@ResponseBody和@Controller。用来创建 RESTful Web 服务控制器,方法默认返回 JSON 或 XML 数据。
@Controller 一般用于传统的 MVC 控制器,需要配合 @ResponseBody 使用来返回非视图内容的数据。
3.@Configuration 和 @Bean:
@Configuration 表示该类是一个配置类,@Bean 用于在方法上定义新的 Spring bean。
21.SpringBoot自动配置原理?
1.通过@SpringBootConfiguration 引入了@EnableAutoConfiguration(负责启动自动配置功能)
2.@EnableAutoConfiguration引入了@lmport
3.Spring容器启动时:加载loc容器时会解析@Import 注解
4.@lmport导入了一个DeferredlmportSelector,它会使SpringBoot的自动配置类的顺序在最后,这样方便我们扩展和覆盖
5.然后读取所有的**/META-INF/spring.factories**文件
6.过滤出所有AutoConfiqurtionClass类型的类
7.最后通过@ConditionalOnClass排除无效的自动配置类
22.什么是循环依赖?Spring怎么解决循环依赖问题?
循环依赖:2个或多个对象之间相互依赖,导致Bean无法实例化(因为Spring中会优先创建Bean的属性对象)
出现循环依赖的场景:
- Spring的Bean的实例化
- JVM使用 引用计数法 进行垃圾回收,可能出现循环依赖导致内存泄漏。
- 线程互相占用对方的锁,发生死锁
Spring内部通过三级缓存解决循环依赖
- 1.一级缓存(singletonObjects 单例对象) :存放已经实例化、属性填充、初始化最终形态的 Bean。
- 2.二级缓存(earlySingletonObjects 早期单例对象):存放提前暴露的、尚未属性填充的过渡 Bean。也就是
级缓存中 objectFactory 产生的对象,与三级缓存配合使用的,可以防止 AOP 的情况下,每次调用
0bjectFactory.getobject()都是会产生新的代理对象的。 - 3.三级缓存(singletonFactories 单例工厂):存放 objectFactory ,objectFactory 的 getobject()方法最终调
用 getEarlyBeanReference()方法可以生成原始 Bean 对象或者AOP代理对象。
Spring创建Bean流程
1.当 Spring 创建 A 时发现 A 依赖了 B,又去创建 B,B 依赖了 A ,又去创建 A;
2.在 B 创建 A 的时候, A此时还没有初始化完成,因此在 -二级缓存 中肯定没有 A;
3.那么此时就去三级缓存中调用 getobiect()方法最终调用 getEarlyBeanReference()方法去生成并获取 A 的前
期暴露的对象。
从三级缓存中移除,并且将前期暴露对象放入到二级缓存中,那么B就将它注入
4.然后就将这个 objectFactory 到依赖,来支持循环依赖。
注: 只用两级缓存够吗?
在没有 AOP 的情况下,确实可以只使用一级和三级缓存来解决循环依赖问题。
当涉及到 AOP 时,二级缓存非常重要,避免了同一个 Bean 有多个代理对象的问题。