Spring 面经

1、Spring 是什么?特性?有哪些模块?

Spring 是一个轻量级、非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的框架。

1.1 Spring 有哪些特性呢?

在这里插入图片描述

  1. IoC 和 DI 的支持:Spring 的核心就是一个大的工厂容器,可以维护所有对象的创建和依赖关系,Spring 工厂用于生成 Bean,并且管理 Bean 的生命周期,实现高内聚低耦合的设计理念。
  2. AOP 编程的支持:Spring 提供了面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等切面功能。
  3. 声明式事务的支持:支持通过配置就来完成对事务的管理,而不需要通过硬编码的方式,以前重复的一些事务提交、回滚的 JDBC 代码,都可以不用自己写了。
  4. 快捷测试的支持:Spring 对 Junit 提供支持,可以通过注解快捷地测试 Spring 程序。
  5. 快速集成功能:方便集成各种优秀框架,Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持。
  6. 复杂 API 模板封装:Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了模板化的封装,这些封装 API 的提供使得应用难度大大降低。

1.2 简单说一下什么是AOP 和 IoC?

AOP:面向切面编程,是一种编程范式,它的主要作用是将那些与核心业务逻辑无关,但是对多个对象产生影响的公共行为封装起来,如日志记录、性能统计、事务等。

IoC:控制反转,是一种设计思想,它的主要作用是将对象的创建和对象之间的调用过程交给 Spring 容器来管理。

2、Spring 有哪些模块呢?

Spring 框架是分模块存在,除了最核心的 Spring Core Container 是必要模块之外,其他模块都是可选,大约有 20 多个模块。最主要的七大模块:

  1. Spring Core:Spring 核心,它是框架最基础的部分,提供 IoC 和依赖注入 DI 特性。
  2. Spring Context:Spring 上下文容器,它是 BeanFactory 功能加强的一个子接口。
  3. Spring Web:它提供 Web 应用开发的支持。
  4. Spring MVC:它针对 Web 应用中 MVC 思想的实现。
  5. Spring DAO:提供对 JDBC 抽象层,简化了 JDBC 编码,同时,编码更具有健壮性。
  6. Spring ORM:它支持用于流行的 ORM 框架的整合,比如:Spring + Hibernate、Spring + iBatis、Spring + JDO 的整合等。
  7. Spring AOP:即面向切面编程,它提供了与 AOP 兼容的编程实现。

3、Spring 有哪些常用注解呢?

Spring 提供了大量注解简化 Java 应用的开发和配置,主要用于 Web 开发、往容器注入 Bean、AOP、事务控制等。
在这里插入图片描述

3.1 Web 开发方面有哪些注解呢?

  1. @Controller:用于标注控制层组件。
  2. @RestController:是 @Controller 和 @ResponseBody 的结合体,返回 JSON 数据时使用。
  3. @RequestMapping:用于映射请求 URL 到具体的方法上,还可以细分为:
    @PostMapping:只能用于处理 POST 请求
    @DeleteMapping:只能用于处理 DELETE 请求
    @PutMapping:只能用于处理 PUT 请求
    @GetMapping:只能用于处理 GET 请求
  4. @ResponseBody:直接将返回的数据放入 HTTP 响应正文中,一般用于返回 JSON 数据。
  5. @RequestBody:表示一个方法参数应该绑定到 Web 请求体。
  6. @PathVariable:用于接收路径参数,比如 @RequestMapping(“/hello/{name}”),name 就是路径参数。
  7. @RequestParam:用于接收请求参数。比如 @RequestParam(name = “key”) String key,key 就是请求参数。

3.2 容器类注解有哪些呢?

  1. @Component:标识一个类为 Spring 组件,使其能够被 Spring 容器自动扫描和管理。
  2. @Controller:标识一个控制器组件(控制层)。
  3. @Service:标识一个业务逻辑组件(服务层)。
  4. @Repository:标识一个数据访问组件(持久层)。
  5. @Autowired:按类型自动注入依赖。
  6. @Resource:按名称自动注入依赖。
  7. @Configuration:用于定义配置类,可替换 XML 配置文件。
  8. @Value:用于将 Spring Boot 中 application.properties 配置的属性值赋值给变量。

3.3 AOP 方面有哪些注解呢?

@Aspect 用于声明一个切面,可以配合其他注解一起使用,比如:
@After:在方法执行之后执行。
@Before:在方法执行之前执行。
@Around:方法前后均执行。
@PointCut:定义切点,指定需要拦截的方法。

3.4 事务注解有哪些?

主要就是 @Transactional,用于声明一个方法需要事务支持。

4、Spring 中应用了哪些设计模式呢?

在这里插入图片描述

  1. 工厂模式:BeanFactory 和 ApplicationContext,实现 Bean 的创建和管理。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = context.getBean(MyBean.class);
  1. 单例模式:这样可以保证 Bean 的唯一性,减少系统开销。
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService1 = context.getBean(MyService.class);
MyService myService2 = context.getBean(MyService.class);

// This will print "true" because both references point to the same instance
System.out.println(myService1 == myService2);
  1. 代理模式:来实现 AOP 横切关注点(如事务管理、日志记录、权限控制等)
@Transactional
public void myTransactionalMethod() {
    // 方法实现
}

4.1 Spring如何实现单例模式?

Spring 通过 IOC 容器实现单例模式,具体步骤是:单例 Bean 在容器初始化时创建并使用 DefaultSingletonBeanRegistry 提供的 singletonObjects 进行缓存。在请求 Bean 时,Spring 会先从缓存中获取。

// 单例缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

public Object getSingleton(String beanName) {
    return this.singletonObjects.get(beanName);
}

protected void addSingleton(String beanName, Object singletonObject) {
    this.singletonObjects.put(beanName, singletonObject);
}

5、Spring 容器、Web 容器之间的区别?

Spring 容器是 Spring 框架的核心部分,负责管理应用程序中的对象生命周期和依赖注入。

Web 容器(也称 Servlet 容器),是用于运行 Java Web 应用程序的服务器环境,支持 Servlet、JSP 等 Web 组件。常见的 Web 容器包括 Apache Tomcat、Jetty等。

Spring MVC 是 Spring 框架的一部分,专门用于处理 Web 请求,基于 MVC(Model-View-Controller)设计模式。

6、说一说什么是 IoC、DI?

所谓的 IoC,就是由容器来控制对象的生命周期和对象之间的关系。控制对象生命周期的不再是引用它的对象,而是容器,这就叫控制反转(Inversion of Control)。于是,对于某个对象来说,以前是它控制它依赖的对象,现在是所有对象都被 Spring 控制。IOC 是一种思想,DI 是实现 IOC 的具体方式,比如说利用注入机制(如构造器注入、Setter 注入)将依赖传递给目标对象。

6.1 为什么要使用 IoC 呢?

在平时的 Java 开发中,如果我们要实现某一个功能,可能至少需要两个以上的对象来协助完成,在没有 Spring 之前,每个对象在需要它的合作对象时,需要自己 new 一个,比如说 A 要使用 B,A 就对 B 产生了依赖,也就是 A 和 B 之间存在了一种耦合关系。有了 Spring 之后,就不一样了,创建 B 的工作交给了 Spring 来完成,Spring 创建好了 B 对象后就放到容器中,A 告诉 Spring 我需要 B,Spring 就从容器中取出 B 交给 A 来使用。这就是 IoC 的好处,它降低了对象之间的耦合度,使得程序更加灵活,更加易于维护。

7、说说 BeanFactory 和 ApplicantContext?

BeanFactory 位于整个 Spring IoC 容器的顶端,ApplicationContext 算是 BeanFactory 的子接口。最主要的方法就是 getBean(),这个方法负责从容器中返回特定名称或者类型的 Bean 实例。ApplicationContext 继承了 HierachicalBeanFactory 和 ListableBeanFactory 接口,是 BeanFactory 的自动挡版本,是 Spring 应用的默认方式。
在这里插入图片描述

8、你知道 Spring 容器启动阶段会干什么吗?

Spring 的 IoC 容器工作的过程可以划分为两个阶段:容器启动阶段和 Bean 实例化阶段。其中容器启动阶段主要做的工作是加载和解析配置文件,保存到对应的 Bean 定义中。
在这里插入图片描述

8.1 说说 Spring 的 Bean 实例化方式

  1. 构造方法的方式:在类上使用 @Component(或@Service、@Repository 等特定于场景的注解)标注类,然后通过构造方法注入依赖。
@Component
public class ExampleBean {
    private DependencyBean dependency;

    @Autowired
    public ExampleBean(DependencyBean dependency) {
        this.dependency = dependency;
    }
}
  1. 静态工厂的方式:在这种方式中,Bean 是由一个静态方法创建的,而不是直接通过构造方法。
public class ClientService {
    private static ClientService clientService = new ClientService();

    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}
  1. 实例工厂方法的方式:与静态工厂方法相比,实例工厂方法依赖于某个类的实例来创建 Bean。这通常用在需要通过工厂对象的非静态方法来创建 Bean 的场景。
public class ServiceLocator {
    public ClientService createClientServiceInstance() {
        return new ClientService();
    }
}
  1. FactoryBean 接口的方式:FactoryBean 是一个特殊的 Bean 类型,可以在 Spring 容器中返回其他对象的实例。通过实现 FactoryBean 接口,可以自定义实例化逻辑,这对于构建复杂的初始化逻辑非常有用。
public class ToolFactoryBean implements FactoryBean<Tool> {
    private int factoryId;
    private int toolId;

    @Override
    public Tool getObject() throws Exception {
        return new Tool(toolId);
    }

    @Override
    public Class<?> getObjectType() {
        return Tool.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    // setter and getter methods for factoryId and toolId
}

9、你是怎么理解 Bean 的?

Bean 是指由 Spring 容器管理的对象,它的生命周期由容器控制,包括创建、初始化、使用和销毁。通过三种方式声明:注解方式、XML 配置、Java 配置。

  1. 使用 @Component、@Service、@Repository、@Controller 等注解定义,主流。
  2. 基于 XML 配置,Spring Boot 项目已经不怎么用了。
  3. 使用 Java 配置类创建 Bean:
@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }
}

9.1 @Component 和 @Bean 的区别

@Component 是 Spring 提供的一个类级别注解,由 Spring 自动扫描并注册到 Spring 容器中。@Bean 是一个方法级别的注解,用于显式地声明一个 Bean,当我们需要第三方库或者无法使用 @Component 注解类时,可以使用 @Bean 来将其实例注册到容器中。

10、能说一下 Bean 的生命周期吗?

Bean 的生命周期大致分为五个阶段:
在这里插入图片描述

  1. 实例化:Spring 容器根据 Bean 的定义创建 Bean 的实例,相当于执行构造方法,也就是 new 一个对象。
  2. 属性赋值:相当于执行 setter 方法为字段赋值。
  3. 初始化:初始化阶段允许执行自定义的逻辑,比如设置某些必要的属性值、开启资源、执行预加载操作等,以确保 Bean 在使用之前是完全配置好的。
  4. 销毁:相当于执行 = null,释放资源。

11、为什么 IDEA 不推荐使用 @Autowired 注解注入 Bean?

这是因为字段注入的方式不能像构造方法那样使用 final 注入不可变对象。

11.1 @Autowired 和 @Resource 注解的区别?

@Autowired 是 Spring 提供的注解,按类型(byType)注入。
@Resource 是 Java EE 提供的注解,按名称(byName)注入。

12、Spring 有哪些自动装配的方式?

  1. byName:根据名称进行自动匹配。
  2. byType:根据类型进行自动匹配。
  3. constructor:与 byType 类似, 针对构造函数注入。
  4. autodetect:如果 Bean 提供无参构造函数,则采用 byType,否则采用 constructor。

13、Bean 的作用域有哪些?

在 Spring 中,Bean 默认是单例的,即在整个 Spring 容器中,每个 Bean 只有一个实例。可以通过在配置中指定 scope 属性,将 Bean 改为多例(Prototype)模式,这样每次获取的都是新的实例。

@Bean
@Scope("prototype")  // 每次获取都是新的实例
public MyBean myBean() {
    return new MyBean();
}

除了单例和多例,Spring 还支持其他作用域,如请求作用域(Request)、会话作用域(Session)等,适合 Web 应用中特定的使用场景。
在这里插入图片描述

  1. request:每一次 HTTP 请求都会产生一个新的 Bean,该 Bean 仅在当前 HTTP Request 内有效。
  2. session:同一个 Session 共享一个 Bean,不同的 Session 使用不同的 Bean。
  3. globalSession:同一个全局 Session 共享一个 Bean,Spring 5 中已经移除。

14、Spring 中的单例 Bean 会存在线程安全问题吗?

Spring Bean 的默认作用域是单例(Singleton),这意味着 Spring 容器中只会存在一个 Bean 实例,并且该实例会被多个线程共享。如果单例 Bean 是无状态的,也就是没有成员变量,那么这个单例 Bean 是线程安全的。比如 Spring MVC 中的 Controller、Service、Dao 等,基本上都是无状态的。但如果 Bean 的内部状态是可变的,且没有进行适当的同步处理,就可能出现线程安全问题。
在这里插入图片描述

14.1 单例 Bean 线程安全问题怎么解决呢?

  1. 使用局部变量,局部变量是线程安全的,因为每个线程都有自己的局部变量副本。尽量使用局部变量而不是共享的成员变量。
  2. 尽量使用无状态的 Bean,即不在 Bean 中保存任何可变的状态信息。、
  3. 同步访问。如果 Bean 中确实需要保存可变状态,可以通过 synchronized 关键字或者 Lock 接口来保证线程安全。
public class MyService {
    private int sharedVar;

    public synchronized void increment() {
        sharedVar++;
    }
}

或者将 Bean 中的成员变量保存到 ThreadLocal 中,ThreadLocal 可以保证多线程环境下变量的隔离。

public class MyService {
    private ThreadLocal<Integer> localVar = ThreadLocal.withInitial(() -> 0);

    public void process() {
        localVar.set(localVar.get() + 1);
    }
}

再或者使用线程安全的工具类,比如说 AtomicInteger、ConcurrentHashMap、CopyOnWriteArrayList 等。

public class MyService {
    private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

    public void putValue(String key, String value) {
        map.put(key, value);
    }
}
  1. 将 Bean 定义为原型作用域(Prototype)。原型作用域的 Bean 每次请求都会创建一个新的实例,因此不存在线程安全问题。

15、说说循环依赖?

A 依赖 B,B 依赖 A,或者 C 依赖 C,就成了循环依赖。
在这里插入图片描述

15.1 Spring 可以解决哪些情况的循环依赖?

在这里插入图片描述
第四种可以,第五种不可以是因为 Spring 在创建 Bean 时默认会根据自然排序进行创建,所以 A 会先于 B 进行创建。

16、Spring 怎么解决循环依赖呢?

Spring 通过三级缓存机制来解决循环依赖:

  1. 一级缓存:存放完全初始化好的单例 Bean。
  2. 二级缓存:存放正在创建但未完全初始化的 Bean 实例。
  3. 三级缓存:存放 Bean 工厂对象,用于提前暴露 Bean。

16.1 三级缓存解决循环依赖的过程是什么样的?

  1. 实例化 Bean 时,将其早期引用放入三级缓存。
  2. 其他依赖该 Bean 的对象,可以从缓存中获取其引用。
  3. 初始化完成后,将 Bean 移入一级缓存。
    在这里插入图片描述

假如 A、B 两个类发生循环依赖:
在这里插入图片描述
A 实例的初始化过程:

  1. 创建 A 实例,实例化的时候把 A 的对象⼯⼚放⼊三级缓存,表示 A 开始实例化了,虽然这个对象还不完整,但是先曝光出来让大家知道。
    在这里插入图片描述

  2. A 注⼊属性时,发现依赖 B,此时 B 还没有被创建出来,所以去实例化 B。

  3. 同样,B 注⼊属性时发现依赖 A,它就从缓存里找 A 对象。依次从⼀级到三级缓存查询 A。发现可以从三级缓存中通过对象⼯⼚拿到 A,虽然 A 不太完善,但是存在,就把 A 放⼊⼆级缓存,同时删除三级缓存中的 A,此时,B 已经实例化并且初始化完成了,把 B 放入⼀级缓存。
    在这里插入图片描述

  4. 接着 A 继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除⼆级缓存中的 A,同时把 A 放⼊⼀级缓存。

  5. 最后,⼀级缓存中保存着实例化、初始化都完成的 A、B 对象。
    在这里插入图片描述

17、为什么要三级缓存?二级不⾏吗?

不行,主要是为了生成代理对象。因为三级缓存中放的是生成具体对象的匿名内部类,获取 Object 的时候,可以生成代理对象,也可以返回普通对象。使⽤三级缓存主要是为了保证不管什么时候使用的都是⼀个对象。

假设只有二级缓存:往二级缓存中放的显示⼀个普通的 Bean 对象,Bean 初始化过程中,通过 BeanPostProcessor 去生成代理对象之后,覆盖掉二级缓存中的普通 Bean 对象,那么可能就导致取到的 Bean 对象不一致了。

17.1 如果缺少第二级缓存会有什么问题?

如果没有二级缓存,Spring 无法在未完成初始化的情况下暴露 Bean。会导致代理 Bean 的循环依赖问题,因为某些代理逻辑无法在三级缓存中提前暴露。最终可能抛出 BeanCurrentlyInCreationException

18、@Autowired 的实现原理?

实在 Bean 的初始化阶段,会通过 Bean 后置处理器来进行一些前置和后置的处理。实现 @Autowired 的功能,也是通过后置处理器来完成的。这个后置处理器就是 AutowiredAnnotationBeanPostProcessor。

  1. Spring 在创建 bean 的过程中,最终会调用到 doCreateBean()方法,在 doCreateBean()方法中会调用 populateBean ()方法,来为 bean 进行属性填充,完成自动装配等工作。
  2. 在 populateBean()方法中一共调用了两次后置处理器,第一次是为了判断是否需要属性填充,如果不需要进行属性填充,那么就会直接进行 return,如果需要进行属性填充,那么方法就会继续向下执行,后面会进行第二次后置处理器的调用,这个时候,就会调用到 AutowiredAnnotationBeanPostProcessor 的 postProcessPropertyValues()方法,在该方法中就会进行@Autowired 注解的解析,然后实现自动装配。

19、说说什么是 AOP?

AOP 指的是面向切面编程,把业务逻辑中相同的代码抽取到一个独立的模块中,实现了业务逻辑和通用逻辑的分离。
在这里插入图片描述

19.1 AOP 有哪些核心概念?

  1. 切面(Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象。
  2. 连接点(Join Point):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中,连接点指的是被拦截到的方法,实际上连接点还可以是字段或者构造方法。
  3. 切点(Pointcut):对连接点进行拦截的定位
  4. 通知(Advice):指拦截到连接点之后要执行的代码,也可以称作增强。
  5. 目标对象 (Target):代理的目标对象。
  6. 引介(introduction):一种特殊的增强,可以动态地为类添加一些属性和方法。
  7. 织入(Weabing):织入是将增强添加到目标类的具体连接点上的过程。

19.2 织入有哪几种方式?

  1. 编译期织入:切面在目标类编译时被织入。
  2. 类加载期织入:切面在目标类加载到 JVM 时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。
  3. 运行期织入:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。

Spring AOP 采用运行期织入,而 AspectJ 可以在编译期织入和类加载时织入。

19.3 AspectJ 是什么?

AspectJ 是一个 AOP 框架,它可以做很多 Spring AOP 干不了的事情,比如说支持编译时、编译后和类加载时织入切面。并且提供更复杂的切点表达式和通知类型。

// 定义一个切面
@Aspect
public class LoggingAspect {

    // 定义一个切点,匹配 com.example 包下的所有方法
    @Pointcut("execution(* com.example..*(..))")
    private void selectAll() {}

    // 定义一个前置通知,在匹配的方法执行之前执行
    @Before("selectAll()")
    public void beforeAdvice() {
        System.out.println("A method is about to be executed.");
    }
}

19.4 AOP 有哪些环绕方式?

AOP 一般有 5 种环绕方式:

  1. 前置通知 (@Before):在目标方法执行之前执行。
  2. 返回通知 (@AfterReturning):在目标方法成功执行并返回结果后执行。
  3. 异常通知 (@AfterThrowing):在目标方法抛出异常后执行。
  4. 后置通知 (@After):无论目标方法是否成功执行(无论是否抛出异常),都会执行的后置处理。
  5. 环绕通知 (@Around):最强大也是最复杂的通知类型,可以在目标方法执行前后都执行自定义逻辑,甚至可以修改方法的行为或决定是否执行目标方法。

多个切面的情况下,可以通过 @Order 指定先后顺序,数字越小,优先级越高。

19.5 Spring AOP 发生在什么时候?

Spring AOP 基于运行时代理机制,这意味着 Spring AOP 是在运行时通过动态代理生成的,而不是在编译时或类加载时生成的。在 Spring 容器初始化 Bean 的过程中,Spring AOP 会检查 Bean 是否需要应用切面。如果需要,Spring 会为该 Bean 创建一个代理对象,并在代理对象中织入切面逻辑。这一过程发生在 Spring 容器的后处理器(BeanPostProcessor)阶段。

19.6 简单总结一下 AOP

AOP,也就是面向切面编程,是一种编程范式,旨在提高代码的模块化。比如说可以将日志记录、事务管理等分离出来,来提高代码的可重用性。

19.7 AOP和 OOP 的关系?

AOP 和 OOP 是互补的编程思想:

  1. AOP 提供了解决横切关注点(如日志、权限、事务等)的机制,将这些逻辑集中管理。
  2. OOP 通过类和对象封装数据和行为,专注于核心业务逻辑。

20、AOP的使用场景有哪些?

AOP 的使用场景有很多,比如说日志记录、事务管理、权限控制、性能监控等。我的项目中主要利用 AOP 来放重复提交。

  1. 第一步,自定义注解作为切点。
  2. 第二步,配置 AOP 切面。
  3. 第三步,在使用的地方加上自定义注解。
  4. 第四步,当接口被调用时,就可以看到防重复提交效果。

21、说说 JDK 动态代理和 CGLIB 代理?

22、

23、

24、

25、

26、

27、

28、

29、

30、

31、

32、

33、

34、

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值