余声-个人博客


  • 首页

  • 分类

  • 归档

  • 标签

👌解释一下 Spring 的 ioc 控制反转

发表于 2025-03-10 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌解释一下 Spring 的 ioc 控制反转?

口语化回答

好的,面试官,控制反转通过将对象的创建和依赖关系的管理交给Spring IoC容器,极大地提高了代码的模块化和可维护性。IoC的主要实现方式是依赖注入,其中通过构造函数注入、Setter方法注入和字段注入等形式来注入,这样 Spring容器能够自动管理对象的依赖关系,使得应用程序代码更加简洁。以上。

题目解析

重点高频题。主要思路就是聊 ioc 是什么,再说说 di 的形式,最后说一下好处即可。

面试得分点

反转,解耦,注入

题目详细答案

什么是控制反转(IoC)?

在传统的编程模型中,应用程序代码通常直接控制对象的创建和依赖关系。例如,一个对象需要依赖另一个对象时,通常会在代码中直接创建依赖对象。这种方式使得代码紧密耦合,不利于测试和维护。

控制反转的理念是将这种控制权从应用程序代码中移除,转而交给一个容器来管理。这个容器就是Spring IoC容器。通过这种方式,对象的创建和依赖关系的管理被反转了,应用程序代码不再负责这些任务,而是由容器来处理。

依赖注入(DI)

依赖注入是实现控制反转的一种方式。它主要有以下几种形式:

构造函数注入:通过构造函数将依赖对象传递给被依赖对象。

1
2
3
4
5
6
7
public class Service {
private final Repository repository;

public Service(Repository repository) {
this.repository = repository;
}
}

Setter方法注入:通过Setter方法将依赖对象注入到被依赖对象中。

1
2
3
4
5
6
7
public class Service {
private Repository repository;

public void setRepository(Repository repository) {
this.repository = repository;
}
}

字段注入:直接在字段上使用注解进行注入。

1
2
3
4
public class Service {
@Autowired
private Repository repository;
}

Spring IoC 容器

Spring IoC容器负责管理应用程序中对象的生命周期和依赖关系。它的主要职责包括:

对象的创建:根据配置文件或注解创建对象。

依赖注入:将对象的依赖注入到相应的对象中。

对象的销毁:在适当的时候销毁对象,释放资源。

配置方式

Spring IoC容器可以通过多种方式进行配置:

XML配置:通过XML文件定义Bean及其依赖关系。

1
2
3
4
5
6
<beans>
<bean id="repository" class="com.example.Repository"/>
<bean id="service" class="com.example.Service">
<constructor-arg ref="repository"/>
</bean>
</beans>

Java配置:通过Java类和注解定义Bean及其依赖关系。

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class AppConfig {
@Bean
public Repository repository() {
return new Repository();
}

@Bean
public Service service() {
return new Service(repository());
}
}

注解配置:通过注解(如@Component,@Autowired)自动扫描和注入Bean。

1
2
3
4
5
6
7
8
9
@Component
public class Repository {
}

@Component
public class Service {
@Autowired
private Repository repository;
}

👌什么是动态代理

发表于 2025-03-09 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌什么是动态代理

口语化回答

好的,面试官。动态代理主要是在我们的程序运行时动态生成一种代理类的机制,让我们在不修改原始类的情况下,可以帮助原始类增加一些功能。通常有两种方式,一种是 jdk 的 proxy,一种是 cglib。动态代理在 spring 框架中的应用也非常常见。主要的 aop 机制就是通过动态代理实现。那么动态代理主要的优势就是解耦,减少代码重复,同时增强现有代码。以上。

题目解析

问到这道题的时候,大家一定要 cglib 和 spring 框架上面靠一靠,去埋点,让面试官继续追问下面的题目。

面试得分点

增强行为,动态代理过程,减少重复代码

题目详细答案

动态代理是一种在运行时动态生成代理类的机制,它允许我们在不修改原始类的情况下增强或修改其行为。动态代理是通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现的。

动态代理的基本概念

代理类(Proxy Class):一个代理类是一个实现了一个或多个接口的类,它可以在运行时动态生成。代理类的实例可以用来代替原始对象,并在调用方法时执行额外的逻辑。

调用处理器(Invocation Handler):调用处理器是一个实现了InvocationHandler接口的类,它定义了代理类的方法调用逻辑。每次代理对象的方法被调用时,调用处理器的invoke方法都会被执行。

动态代理的作用和优势

1、 解耦业务逻辑和通用功能:动态代理允许将通用功能(如日志记录、事务管理、安全性检查等)从业务逻辑中分离出来,从而提高代码的模块化和可维护性。

2、 灵活性:动态代理在运行时生成代理类,不需要在编译时确定代理类,因此具有很大的灵活性。

3、 减少代码重复:通过动态代理,可以将通用功能集中到一个地方,从而减少代码重复。

4、 增强现有代码:动态代理允许在不修改现有代码的情况下增强其功能。

动态代理的应用场景

1、AOP(面向切面编程):动态代理是实现 AOP 的核心技术之一,通过动态代理可以在方法执行前后添加横切关注点(如日志记录、事务管理等)。

2、 远程方法调用(RMI):动态代理可以用来实现客户端和服务器之间的远程方法调用。

3、 装饰器模式:动态代理可以用来实现装饰器模式,在不修改原始类的情况下增强其功能。

4、 框架和中间件:许多框架和中间件(如 Spring、Hibernate 等)都使用动态代理来实现其核心功能。

动态代理通过在运行时生成代理类,提供了一种灵活且强大的方式来增强现有代码的功能,而无需修改原始代码。

👌Spring事务中的隔离级别有哪几种?

发表于 2025-03-06 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Spring事务中的隔离级别有哪几种?

题目详细答案

在 Spring 中,事务的隔离级别(Isolation Level)定义了一个事务与其他事务隔离的程度。隔离级别控制着事务在并发访问数据库时的行为,特别是如何处理多个事务同时读取和修改数据的情况。不同的隔离级别提供了不同的并发性和数据一致性保证。

Spring 提供了以下几种隔离级别,通过@Transactional注解的isolation属性来配置:

DEFAULT:使用底层数据库的默认隔离级别。通常情况下,这个默认值是READ_COMMITTED。

READ_UNCOMMITTED:允许一个事务读取另一个事务尚未提交的数据。可能会导致脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)问题。

READ_COMMITTED:保证一个事务只能读取另一个事务已经提交的数据。可以防止脏读,但可能会导致不可重复读和幻读问题。

REPEATABLE_READ:保证一个事务在读取数据时不会看到其他事务对该数据的修改。可以防止脏读和不可重复读,但可能会导致幻读问题。

SERIALIZABLE:最高的隔离级别。保证事务按顺序执行,完全隔离。可以防止脏读、不可重复读和幻读问题,但并发性最低,可能导致性能下降。

代码 Demo

假设有一个服务类MyService,可以通过不同的隔离级别来配置事务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class MyService {

@Transactional(isolation = Isolation.READ_COMMITTED)
public void readCommittedMethod() {
// 数据库操作
}

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void repeatableReadMethod() {
// 数据库操作
}

@Transactional(isolation = Isolation.SERIALIZABLE)
public void serializableMethod() {
// 数据库操作
}
}

隔离级别的选择

选择合适的隔离级别取决于具体的业务需求和对并发性和数据一致性的要求:

READ_UNCOMMITTED:适用于对数据一致性要求不高,且需要最高并发性的场景。

READ_COMMITTED:适用于大多数应用,能够防止脏读,提供较好的并发性和数据一致性平衡。

REPEATABLE_READ:适用于需要防止脏读和不可重复读,但可以容忍幻读的场景。

SERIALIZABLE:适用于对数据一致性要求极高的场景,尽管会牺牲并发性。

👌Spring事务传播行为

发表于 2025-03-05 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Spring事务传播行为

口语化回答

好的,面试官,传播行为主要有几种,REQUIRED,如果当前存在事务,则加入,否则创建一个新的。这是默认的事务传播行为。还有就是其他的不常用的事务形式,比如REQUIRES_NEW,总是创建一个新的事务。如果当前存在事务,则挂起当前事务。SUPPORTS总是创建一个新的事务。使用方式就是@Transactional 里面的propagation 属性进行直接指定即可。其他不常见的就不多提了,就是围绕有事务,无事务,嵌套,以及异常来分不同情况选择事务创建,以上。

题目解析

问的比较少,大家就记住前两种就 ok 了。如果全部理解的口诀,就是,有用,没有创建,异常建,没则抛异常。

面试得分点

required,Transactional

题目详细答案

Spring 事务传播行为定义了在事务方法被调用时,事务如何传播。Spring 提供了多种传播行为,允许开发者定义事务的边界和行为。这些传播行为通过@Transactional注解的propagation属性来配置。

传播行为类型

REQUIRED(默认值):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

REQUIRES_NEW:总是创建一个新的事务。如果当前存在事务,则挂起当前事务。

SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式继续执行。

NOT_SUPPORTED:总是以非事务方式执行,如果当前存在事务,则挂起当前事务。

MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

NEVER:总是以非事务方式执行,如果当前存在事务,则抛出异常。

NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。

代码 Demo

假设有两个服务类ServiceA和ServiceB,可以通过不同的传播行为来控制事务的传播。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class ServiceA {

@Autowired
private ServiceB serviceB;

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// Some database operations
serviceB.methodB();
// Some more database operations
}
}

@Service
public class ServiceB {

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// Some database operations
}
}

ServiceA的methodA方法使用REQUIRED传播行为,这意味着如果methodA被调用时没有事务存在,它将创建一个新的事务。如果methodA被调用时已经有一个事务存在,它将加入该事务。

ServiceB的methodB方法使用REQUIRES_NEW传播行为,这意味着它总是会创建一个新的事务。如果methodB被调用时已经有一个事务存在,该事务将被挂起,直到methodB的事务完成。

事务传播行为的选择

REQUIRED:大多数情况下使用的默认传播行为,适用于大多数需要事务管理的方法。

REQUIRES_NEW:适用于需要独立事务的情况,例如记录日志、审计等操作,即使外层事务回滚,这些操作也应该提交。

SUPPORTS:适用于可选事务的情况,例如读取操作,可以在事务内或事务外执行。

NOT_SUPPORTED:适用于不需要事务的情况,例如调用外部服务。

MANDATORY:适用于必须在事务内执行的方法,例如严格依赖事务上下文的操作。

NEVER:适用于必须在非事务上下文中执行的方法。

NESTED:适用于需要嵌套事务的情况,例如需要在一个事务内执行多个子事务,并且可以单独回滚子事务。

👌Spring 实例化 bean 有哪几种方式?

发表于 2025-03-04 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Spring 实例化 bean 有哪几种方式?

口语化回答

好的,面试官。实例化 bean 的方式,主要有构造器实例化,bean 注解实例化,不常用的用静态工厂和实例工厂实例化。构造器实例化主要是通过调用类的构造器来实例化 Bean。构造器可以是无参构造器,也可以是有参构造器。配合 bean 标签的 xml 配置形式,放入容器中。bean 注解的方式就更加常用,尤其是现在都是 boot 的形式。一个注解就可以放入容器中。以上。

题目解析

这道题也很常问,同样的还是考察是否平时在学习中用过。应届生比较常问,工作的小伙伴问的概率不大。

面试得分点

构造器实例化,bean 方式,工厂方式

题目详细答案

通过构造器实例化

这是最常见的方式之一,Spring 可以通过调用类的构造器来实例化 Bean。构造器可以是无参构造器,也可以是有参构造器。

无参构造器

1
2
3
4
5
public class MyBean {
public MyBean() {
// 无参构造器
}
}

配置方式:

1
<bean id="myBean" class="com.example.MyBean"/>

有参构造器

1
2
3
4
5
6
7
public class MyBean {
private String name;

public MyBean(String name) {
this.name = name;
}
}

配置方式:

1
2
3
<bean id="myBean" class="com.example.MyBean">
<constructor-arg value="exampleName"/>
</bean>

通过静态工厂方法实例化

Spring 可以通过调用静态工厂方法来实例化 Bean。这种方式适用于需要复杂初始化逻辑的情况。

1
2
3
4
5
public class MyBeanFactory {
public static MyBean createInstance(String name) {
return new MyBean(name);
}
}

配置方式:

1
2
3
<bean id="myBean" class="com.example.MyBeanFactory" factory-method="createInstance">
<constructor-arg value="exampleName"/>
</bean>

通过实例工厂方法实例化

这种方式类似于静态工厂方法,不同之处在于实例工厂方法需要先实例化工厂类,然后调用工厂类的实例方法来创建 Bean。

1
2
3
4
5
public class MyBeanFactory {
public MyBean createInstance(String name) {
return new MyBean(name);
}
}

配置方式:

1
2
3
4
<bean id="myBeanFactory" class="com.example.MyBeanFactory"/>
<bean id="myBean" factory-bean="myBeanFactory" factory-method="createInstance">
<constructor-arg value="exampleName"/>
</bean>

通过 FactoryBean 接口实例化

Spring 提供了一个FactoryBean接口,允许开发者定制 Bean 的创建逻辑。实现FactoryBean接口的类可以被用作工厂来生成其他 Bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyFactoryBean implements FactoryBean<MyBean> {
private String name;

public void setName(String name) {
this.name = name;
}

@Override
public MyBean getObject() throws Exception {
return new MyBean(name);
}

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

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

配置方式:

1
2
3
4
<bean id="myFactoryBean" class="com.example.MyFactoryBean">
<property name="name" value="exampleName"/>
</bean>
<bean id="myBean" factory-bean="myFactoryBean" factory-method="getObject"/>

通过 @Bean 注解实例化

使用@Bean注解的方法可以用来实例化和配置 Bean。这种方式更加直观和灵活,适合基于 Java 配置的 Spring 应用。

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean("exampleName");
}
}

通过 @Component 注解实例化

使用@Component注解可以将类标记为 Spring 管理的 Bean。结合@ComponentScan注解,Spring 会自动扫描并实例化这些类。

1
2
3
4
@Component
public class MyBean {
// Bean 定义
}

配置方式:

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 配置类
}

Autowired和@Resource的区别

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌@Autowired和@Resource的区别

口语化答案

好的,面试官,Autowired 和 Resource 都是依赖注入注解。一个是 spring 框架带的,一个是 javaee 框架带的。Autowired 主要是类型注入,Resource 是按照名称注入,名称找不到的话,会按照类型进行注入。Autowired 当存在多个的时候,可以配合Qualifier 来进行使用。一般在实际工作中比较常用 Resource。以上。

题目解析

常考题,面试官喜欢问,其实两者比较好区分,记住一个是类型,一个是名称即可。然后就是提供方的不同。

面试得分点

类型注入,名称注入,指定名称。

题目详细答案

@Autowired和@Resource都是用于依赖注入的注解。

@Autowired

来源:Spring 框架。

注入方式:默认按类型注入。

用法:可以用于字段、构造器、Setter 方法或其他任意方法。

可选性:可以与@Qualifier一起使用,以指定具体的 Bean。

处理机制:Spring 的AutowiredAnnotationBeanPostProcessor处理@Autowired注解。

@Resource

来源:由 Java EE 提供。

注入方式:默认按名称注入,如果按名称找不到,则按类型注入。

用法:可以用于字段或 Setter 方法。

属性:可以指定name和type属性。

处理机制:Spring 的CommonAnnotationBeanPostProcessor处理@Resource注解。

详细比较

  1. 注入方式:

@Autowired:默认按类型注入。如果需要按名称注入,可以结合@Qualifier注解使用。

@Resource:默认按名称注入。如果名称匹配失败,则按类型注入。

  1. 属性:

@Autowired:没有name和type属性,但可以使用@Qualifier指定名称。

@Resource:有name和type属性,可以明确指定要注入的 Bean 名称或类型。

  1. 兼容性:

@Autowired:是 Spring 框架特有的注解。

@Resource:是 Java 标准注解,兼容性更广,适用于任何支持 JSR-250 的容器。

  1. 使用场景:

@Autowired:在 Spring 应用中更为常见,尤其是在需要按类型注入的场景中。

@Resource:在需要兼容标准 Java EE 规范的应用中更为常见,或者在需要明确指定 Bean 名称时使用。

原文: https://www.yuque.com/jingdianjichi/xyxdsi/bnn9a113lxo4uvpm

👌Rabbitmq如何避免消息丢失?

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌Rabbitmq如何避免消息丢失?

口语化回答

好的,面试官,rabbitmq 主要通过持久化机制,ack 确认机制,镜像等保证消息不丢失。rabbitmq 在创建队列的时候,可以设置为持久化队列,消息也可以设置为持久化消息,这样消息会被写入磁盘。即使重启,也不会产生丢失的问题。确认机制是我们平时最常见的机制,ack,消费者正常消费成功过消息后,会发出 ack 信号。mq 收到之后,才会认为消息成功消费,否则会认为消费失败,此时不会抛弃会让其他消费者继续进行消费。镜像队列是高可用的一种形式。可以确保一个节点挂了,另一个节点可以正常的存储消息,类似 redis 主从复制。以上。

消息持久化

1、 持久化队列:创建队列时,可以将其声明为持久化队列(durable)。持久化队列在 RabbitMQ 重启后依然存在。

1
channel.queueDeclare("queue_name", true, false, false, null);

2、 持久化消息:在发送消息时,可以将消息标记为持久化(persistent)。持久化消息会被写入磁盘,即使 RabbitMQ 重启,消息也不会丢失。

1
2
3
4
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 2 表示持久化消息
.build();
channel.basicPublish("exchange_name", "routing_key", props, messageBody);

消息确认机制(Acknowledgements)

1、 消费者确认:消费者处理完消息后,向 RabbitMQ 发送确认(ack)。如果消费者未确认消息(如消费者崩溃),RabbitMQ 会将消息重新投递给其他消费者。

1
2
3
4
5
// 自动确认关闭
channel.basicConsume("queue_name", false, consumer);

// 消费者处理完消息后手动确认
channel.basicAck(deliveryTag, false);

2、 发布者确认:发布者可以启用发布确认模式(publisher confirms),RabbitMQ 会在消息成功写入队列后发送确认给发布者。

1
2
3
channel.confirmSelect();
channel.basicPublish("exchange_name", "routing_key", null, messageBody);
channel.waitForConfirmsOrDie(); // 等待确认

事务支持

RabbitMQ 支持 AMQP 事务,可以在一个事务中发布多条消息,确保这些消息要么全部成功,要么全部失败。

1
2
3
channel.txSelect();
channel.basicPublish("exchange_name", "routing_key", null, messageBody);
channel.txCommit(); // 提交事务

镜像队列(Mirrored Queues)

RabbitMQ 的集群模式支持镜像队列(也称为高可用队列),可以将队列的数据复制到多个节点上,确保在一个节点故障时,其他节点仍然可以提供服务。

1
2
// 定义队列策略,将队列配置为镜像队列
rabbitmqctl set_policy ha-all "^queue_name$" '{"ha-mode":"all"}'

消费者重试机制

消费者可以实现重试机制,当消息处理失败时,可以将消息重新投递到队列或死信队列,避免消息丢失。

1
2
3
4
5
6
try {
// 处理消息
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
channel.basicNack(deliveryTag, false, true); // 重试
}

👌@Component和@Bean的区别是什么?

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌@Component和@Bean的区别是什么

口语化答案

好的,面试官。component 和 bean 是非常常用的将类注册到容器中的注解。component 需要配合@ComponentScan注解使用,Spring 会自动扫描指定包及其子包中的所有类,找到带有@Component注解的类,并将它们注册为 Bean。@Bean 标记在方法上,方法所在的类需要用@Configuration注解标记。不需要扫描,主动的注入到 spring 容器中。

题目解析

经典的一道问题。两种方式都在实际工作中非常常见的使用。考察平时常见的扫描方式和假设有一个 bean 比如第三方,想要加载到容器中,不是类的形式,应该如何去做。

面试得分点

自动扫描,ComponentScan,方法级别,@Configuration

题目详细答案

@Component

用途:用于将一个类标记为 Spring 组件类,使其被 Spring 容器自动扫描并注册为 Bean。

使用场景:通常用于标记那些需要自动检测和注册为 Bean 的类。

位置:直接标记在类上。

自动扫描:需要配合@ComponentScan注解使用,Spring 会自动扫描指定包及其子包中的所有类,找到带有@Component注解的类,并将它们注册为 Bean。

@Bean

用途:用于定义一个方法,该方法返回一个要注册为 Spring 容器管理的 Bean。

使用场景:通常用于显式定义 Bean,特别是当需要一些复杂的初始化逻辑或需要从第三方库创建 Bean 时。

位置:标记在方法上,方法所在的类需要用@Configuration注解标记。

显式配置:通过显式的 Java 配置方式定义 Bean,而不是通过类路径扫描。

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyComponent myComponent() {
return new MyComponent();
}
}

详细比较

定义方式:

@Component:用于类级别,通过类路径扫描自动检测和注册。

@Bean:用于方法级别,通过显式的 Java 配置注册。

使用场景:

@Component:适用于那些可以通过类路径扫描自动检测的类,通常是应用中的主要组件,如服务类、数据访问类等。

@Bean:适用于需要显式定义的 Bean,尤其是那些需要复杂初始化逻辑或从第三方库创建的 Bean。

配置方式:

@Component:需要配合@ComponentScan使用,Spring 会自动扫描并注册。

@Bean:需要在@Configuration类中定义,显式地通过 Java 配置注册。

灵活性:

@Component:更适合于常规的 Spring 组件,配置较为简单。

@Bean:提供了更高的灵活性,可以在方法中包含复杂的创建逻辑。

选择建议

使用@Component:当你的类是应用中的主要组件,并且可以通过类路径扫描自动检测和注册时。例如,服务类、数据访问类、控制器类等。

使用@Bean:当你需要显式定义 Bean,特别是那些需要复杂初始化逻辑或从第三方库创建的 Bean 时。例如,配置第三方库的 Bean、需要自定义初始化逻辑的 Bean 等。

代码 Demo

使用@Component和@ComponentScan:

1
2
3
4
5
6
7
8
9
10
@Component
public class MyComponent {
// 组件逻辑
}

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 配置类内容
}

使用@Bean和@Configuration:

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyComponent myComponent() {
return new MyComponent();
}
}

AOP

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Aop中的相关术语

口语化答案

哈哈,这个就不多说了,面试官问到什么概念,你就直接解释一下就可以了。

题目详细答案

切面(Aspect)

切面是 AOP 的核心概念之一,它是一个模块,包含了横切关注点的逻辑。切面可以看作是对某些功能(如日志记录、事务管理)的封装,这些功能会在特定的连接点上应用。

连接点(Join Point)

连接点是程序执行过程中可以插入切面的一个点。通常,连接点是方法的调用或执行。AOP 框架允许在这些连接点上插入额外的行为。

通知(Advice)

通知是在切面的某个特定的连接点上执行的动作。通知定义了切面在连接点上的具体行为。根据执行时间的不同,通知可以分为以下几种类型:

  • 前置通知(Before):在目标方法执行之前执行。
  • 后置通知(After):在目标方法执行之后执行(无论方法是否成功完成)。
  • 返回通知(After Returning):在目标方法成功返回之后执行。
  • 异常通知(After Throwing):在目标方法抛出异常时执行。
  • 环绕通知(Around):包围目标方法的执行,可以在方法执行之前和之后自定义行为。

切入点(Pointcut)

切入点是一个表达式,定义了哪些连接点会被切面所影响。切入点表达式用于匹配连接点,从而决定切面应该应用到哪些方法上。常见的切入点表达式语言包括 AspectJ 的表达式语言。

目标对象(Target Object)

目标对象是被一个或多个切面所通知的对象。目标对象是 AOP 代理的实际对象,即被代理的对象。

AOP 代理(AOP Proxy)

AOP 代理是一个对象,用于实现切面契约(即通知方法)并将调用委托给目标对象。代理对象负责在调用目标方法之前或之后执行切面逻辑。AOP 代理可以是 JDK 动态代理(用于代理接口)或 CGLIB 代理(用于代理类)。

织入(Weaving)

织入是将切面应用到目标对象并创建 AOP 代理对象的过程。织入可以在以下几个时机进行:

  • 编译时(Compile-time Weaving):在编译阶段将切面织入到目标类中。
  • 类加载时(Load-time Weaving):在类加载阶段使用类加载器将切面织入到目标类中。
  • 运行时(Runtime Weaving):在运行时通过动态代理将切面织入到目标对象中。

引入(Introduction)

引入是 AOP 提供的一种机制,允许在不修改现有类的情况下向其添加新的方法或属性。引入可以用于增强现有类的功能。

ApplicationContext的实现类有哪些?

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌ApplicationContext的实现类有哪些?

口语化答案

好的,面试官。常用的只要用 classpathxml 的基于 xml 的方式,annotion 的基于注解的方式,不常见的还有 web 和 groovy。在目前的实际情况下,主要是 annotion,注解形式放入到容器中。像老的 tomcat 那种方式,用的是 web 形式,不过现在都是 boot 注解形式够用。groovy 适用于一些动态加载 bean 的方式,通过脚本的形式处理。以上。

题目解析

经典题,问的还比较多,主要是想借此考察一下,你在项目中,都用过什么样的形式的。其实主要答出注解和 xml 这道题就 ok。

面试得分点

xml,注解,web,groovy

题目详细答案

基于XML配置的实现类

ClassPathXmlApplicationContext:从类路径下加载XML配置文件。

1
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

FileSystemXmlApplicationContext:从文件系统路径加载XML配置文件。

1
ApplicationContext context = new FileSystemXmlApplicationContext("C:/path/to/applicationContext.xml");

基于注解配置的实现类

AnnotationConfigApplicationContext:从Java配置类(使用@Configuration注解的类)加载配置。

1
2
3
4
5
6
7
8
9
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

基于Web应用的实现类

XmlWebApplicationContext:专门为Web应用设计的ApplicationContext实现类,从Web应用的上下文中加载XML配置文件。

通常在web.xml中配置:

1
2
3
4
5
6
7
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

AnnotationConfigWebApplicationContext:专门为Web应用设计的ApplicationContext实现类,从Java配置类加载配置

1
2
3
4
5
6
7
8
public class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
container.addListener(new ContextLoaderListener(context));
}
}

基于Groovy配置的实现类

GenericGroovyApplicationContext:从Groovy脚本配置文件加载配置。

1
ApplicationContext context = new GenericGroovyApplicationContext("applicationContext.groovy");

BeanFactory 和 ApplicationContext 的区别?

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌BeanFactory 和 ApplicationContext 的区别?

口语化答案

好的,面试官。BeanFactory和ApplicationContext都是用于管理Bean的容器接口。BeanFactory功能相对简单。提供了Bean的创建、获取和管理功能。默认采用延迟初始化,只有在第一次访问Bean时才会创建该Bean。因为功能较为基础,BeanFactory通常用于资源受限的环境中,比如移动设备或嵌入式设备。ApplicationContext是BeanFactory的子接口,提供了更丰富的功能和更多的企业级特性。默认会在启动时创建并初始化所有单例Bean,支持自动装配Bean,可以根据配置自动注入依赖对象。有多种实现,如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext等。以上

题目解析

经典题,早几年爱考,现在随着时间的发展,问的比较少,不排除古老面试官,应届生可以重点看一下。

面试得分点

初始化时机,延迟,企业级应用场景

题目详细答案

BeanFactory和ApplicationContext都是用于管理Bean的容器接口,它们的功能和用途有所不同。

BeanFactory

BeanFactory是Spring框架的核心接口之一,负责管理和配置应用程序中的Bean。它提供了基本的Bean容器功能,但功能相对简单。BeanFactory提供了Bean的创建、获取和管理功能。它是Spring IoC容器的最基本接口。

BeanFactory默认采用延迟初始化(lazy loading),即只有在第一次访问Bean时才会创建该Bean。这有助于提升启动性能。

因为功能较为基础,BeanFactory通常用于资源受限的环境中,比如移动设备或嵌入式设备。

ApplicationContext

ApplicationContext是BeanFactory的子接口,提供了更丰富的功能和更多的企业级特性。

不仅提供了BeanFactory的所有功能,还提供了更多高级特性,如事件发布、国际化、AOP、自动Bean装配等。

ApplicationContext默认会在启动时创建并初始化所有单例Bean(除非显式配置为延迟初始化)。这有助于在应用启动时尽早发现配置问题。

ApplicationContext支持自动装配Bean,可以根据配置自动注入依赖对象。

ApplicationContext有多种实现,如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext等,适用于不同的配置方式和场景。

具体区别总结

BeanFactory ApplicationContext
初始化时机 延迟初始化,只有在第一次访问Bean时才创建该Bean。 立即初始化,在容器启动时就创建并初始化所有单例Bean。
特性 功能较为基础,只提供Bean的创建、获取和管理功能。 提供更多企业级特性,如事件发布、国际化、AOP、自动装配等。
使用场景 适用于资源受限的环境,或者需要延迟初始化的场景。 适用于大多数企业级应用

代码 Demo

使用 BeanFactory

1
2
3
Resource resource = new ClassPathResource("beans.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
MyBean myBean = (MyBean) beanFactory.getBean("myBean");

使用 ApplicationContext

1
2
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyBean myBean = (MyBean) context.getBean("myBean");

Bean 标签的属性?

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Bean 标签的属性?

口语化回答

好的,面试官。bean 是最长使用的标签,如果是使用 xml 形式。最常见的基本属性就是 id,name,class。分别标识唯一的 bean,bean 的别名和实际要注入的类。也可以通过一些属性实现,bean 开始时候的操作,比如init-method,配置的方法,可以在 bean 初始化的时候,进行执行。bean 还有常见的构造函数注入标签,注入 bean 中的属性。以上。

题目解析

应届生常问,有工作经验的基本不问,目的其实就是为了,考察一下你到底有没有实际使用过 spring 的 bean 的配置。

面试得分点

id,name,class,一些初始化属性

题目详细答案

常见属性

id:Bean的唯一标识符。

1
<bean id="myBean" class="com.example.MyBean"/>

name:Bean的别名,可以为Bean定义一个或多个别名。

1
<bean id="myBean" name="alias1,alias2" class="com.example.MyBean"/>

class:Bean的全限定类名。

1
<bean id="myBean" class="com.example.MyBean"/>

scope:Bean的作用域,常见值包括singleton(默认)、prototype、request、session、globalSession、application。

1
<bean id="myBean" class="com.example.MyBean" scope="prototype"/>

init-method:Bean初始化时调用的方法。

1
<bean id="myBean" class="com.example.MyBean" init-method="init"/>

destroy-method:Bean销毁时调用的方法。

1
<bean id="myBean" class="com.example.MyBean" destroy-method="cleanup"/>

factory-method:用于创建Bean实例的静态工厂方法。

1
<bean id="myBean" class="com.example.MyBeanFactory" factory-method="createInstance"/>

factory-bean:用于创建Bean实例的工厂Bean的名称。

1
2
<bean id="myFactory" class="com.example.MyFactory"/>
<bean id="myBean" factory-bean="myFactory" factory-method="createInstance"/>

依赖注入相关属性

constructor-arg:用于构造函数注入。

1
2
3
4
<bean id="myBean" class="com.example.MyBean">
<constructor-arg value="someValue"/>
<constructor-arg ref="anotherBean"/>
</bean>

property:用于Setter方法注入。

1
2
3
4
<bean id="myBean" class="com.example.MyBean">
<property name="propertyName" value="someValue"/>
<property name="anotherProperty" ref="anotherBean"/>
</bean>

其他常用属性

autowire:自动装配模式,常见值包括no(默认)、byName、byType、constructor、autodetect。

1
<bean id="myBean" class="com.example.MyBean" autowire="byName"/>

depends-on:指定Bean的依赖关系,即在初始化当前Bean之前需要先初始化的Bean。

1
<bean id="myBean" class="com.example.MyBean" depends-on="anotherBean"/>

lazy-init:是否延迟初始化,默认值为false。

1
<bean id="myBean" class="com.example.MyBean" lazy-init="true"/>

primary:当自动装配时,如果有多个候选Bean,可以将某个Bean标记为主要候选者。

1
<bean id="myBean" class="com.example.MyBean" primary="true"/>

配置 demo

1
2
3
4
5
6
<bean id="myBean" class="com.example.MyBean" scope="singleton" init-method="init" destroy-method="cleanup" lazy-init="true" primary="true" autowire="byType">
<constructor-arg value="someValue"/>
<constructor-arg ref="anotherBean"/>
<property name="propertyName" value="someValue"/>
<property name="anotherProperty" ref="anotherBean"/>
</bean>

👌Cglib的Enhancer实现动态代理?

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Cglib的Enhancer实现动态代理?

口语化回答

好的,面试官,cglib 代理相比 jdk 动态代理不同的就是不需要被代理的类实现接口。假设我们现在有一个MyService,其中有一个方法是performTask,我们只需要定义一个新的类,实现MethodInterceptor 接口,然后再里面的 intercept 方法实现需要增强的方法。最终通过 cglib 的enhancer 类,先设置父类,父类就是我们要增强的类,再设置 callback 也就是我们要增强的功能。最后使用 create 就生成了 cglib 的一个代理类。以上

题目解析

和 jdk 的动态代理异曲同工,相比 jdk 反而 cglib 的更常考一点。大家理解一下下面的代码流程就很容易说出其中的过程。

面试得分点

目标类,方法拦截器,创建代理对象

题目详细答案

CGLIB是一种强大的代码生成库,能够在运行时生成代理类。与 JDK 动态代理不同的是,CGLIB 不需要接口,可以直接代理具体类。CGLIB 通过创建目标类的子类并覆盖其中的方法来实现代理。

实现步骤

1、 引入 CGLIB 库:确保在项目中添加 CGLIB 依赖。

2、 创建目标类:定义需要代理的具体类。

3、 创建方法拦截器:实现MethodInterceptor接口,并在intercept方法中定义代理逻辑。

4、 创建代理对象:通过Enhancer类创建代理对象。

代码 Demo

假设我们有一个简单的服务类MyService,通过 CGLIB 动态代理为MyService创建一个代理对象,并在方法调用前后添加日志。

引入 CGLIB 依赖

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

创建目标类

1
2
3
4
5
public class MyService {
public void performTask() {
System.out.println("Performing task");
}
}

创建方法拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class LoggingMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Logging before method execution: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("Logging after method execution: " + method.getName());
return result;
}
}

创建代理对象并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import net.sf.cglib.proxy.Enhancer;

public class MainApp {
public static void main(String[] args) {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();

// 设置目标类为代理类的父类
enhancer.setSuperclass(MyService.class);

// 设置方法拦截器
enhancer.setCallback(new LoggingMethodInterceptor());

// 创建代理对象
MyService proxyInstance = (MyService) enhancer.create();

// 调用代理对象的方法
proxyInstance.performTask();
}
}

详细解释

引入 CGLIB 库:在项目中添加 CGLIB 依赖,以便使用 CGLIB 提供的类和接口。

目标类:MyService是一个具体类,定义了一个方法performTask。

方法拦截器:LoggingMethodInterceptor实现了MethodInterceptor接口。它的intercept方法在代理对象的方法调用时被调用。

intercept方法接收四个参数:obj:代理对象。method:被调用的方法。args:方法参数。proxy:用于调用父类方法的代理对象。

在intercept方法中,我们在方法调用前后添加了日志打印,并通过proxy.invokeSuper调用父类的方法。

创建代理对象:使用Enhancer类创建代理对象。setSuperclass方法设置目标类为代理类的父类。setCallback方法设置方法拦截器。create方法创建代理对象。

使用代理对象:

通过代理对象调用方法时,实际调用的是LoggingMethodInterceptor的intercept方法。

在intercept方法中,首先打印日志,然后通过proxy.invokeSuper调用目标对象的方法,最后再打印日志。

👌Spring事务失效场景?

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Spring事务失效场景?

题目详细答案

Spring事务失效的场景主要有以下几种。

非public方法使用@Transactional

场景描述:Spring事务管理是基于AOP实现的,而AOP对于JDK动态代理或CGLib动态代理只会代理public方法。如果事务方法的访问修饰符为非public,SpringAOP无法正确地代理该方法,从而导致事务失效。

示例代码:事务方法的访问修饰符被设置为private、default或protected。

解决方案:将需要事务管理的方法设置为public。

在同类中的非事务方法调用事务方法

场景描述:Spring的事务管理是通过动态代理实现的,只有通过代理对象调用的方法才能享受到Spring的事务管理。如果在同一个类中,一个没有标记为@Transactional的方法内部调用了一个标记为@Transactional的方法,那么事务是不会起作用的。

解决方案:尽量将事务方法放在不同的类中,或者使用Spring的AopContext.currentProxy()来获取当前类的代理对象,然后通过代理对象调用事务方法。

事务属性设置不当

场景描述:在Spring的事务管理中,如果在一个支持当前事务的方法(比如,已经被标记为@Transactional的方法)中调用了一个需要新事务的方法,如果后者方法抛出了异常,但异常并未被Spring识别为需要回滚事务的异常,那么后者的事务将不会回滚。

异常类型不匹配

场景描述:默认情况下,Spring只有在方法抛出运行时异常或者错误时才会回滚事务。对于检查性异常,即使你在方法中抛出了,Spring也不会回滚事务,除非你在@Transactional注解中显式地指定需要回滚哪些检查性异常。

解决方案:了解Spring事务管理对异常的处理,必要时在@Transactional注解中指定需要回滚的异常类型。

事务拦截器配置错误

场景描述:如果没有正确地配置事务拦截器,例如没有指定切入点或指定了错误的切入点,就会导致Spring事务失效。

事务超时配置错误

场景描述:如果事务超时时间设置得太短,就有可能在事务执行过程中出现超时,从而导致Spring事务失效。

👌Spring使用注解的进行装配的时候,需要什么注解

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Spring使用注解的进行装配的时候,需要什么注解

口语化答案

好的,面试官,其实这个问题的目的就是将 bean 让 spring 扫描到,并识别出要放到容器中。那就涉及到第一个概念,ComponentScan,自动扫描指定的包,配合basePackages 属性。指定包之后能,spring 就会将@Service、@Repository和@Controller 这些带了注解的类,作为 bean 放入到容器中。ComponentScan 也提供了一些比如 excludefilter 想排除某些包,或者加特定包的方式。以上。

题目解析

基础题必看,主要稍微会发散的一个点就是 lazyinit,其他的没有什么东西。正常背一下属性就可以了。

面试得分点

basepackage,includefilter,扫描注解

题目详细答案

@ComponentScan是用于自动扫描指定的包及其子包中的组件类,并将它们注册为 Spring 容器管理的 Bean。这个过程被称为组件扫描(Component Scanning)。

@ComponentScan注解

@ComponentScan注解通常与@Configuration注解一起使用,用于定义 Spring 应用的配置类。在这个配置类中,@ComponentScan指定了要扫描的包路径,Spring 会自动扫描这些包及其子包中的所有类,并将带有特定注解的类(如@Component、@Service、@Repository、@Controller等)注册为 Bean。

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 配置类内容
}

Spring 会扫描com.example包及其子包中的所有类,并将带有@Component、@Service、@Repository和@Controller注解的类注册为 Spring Bean。

详细说明

basePackages属性

basePackages属性用于指定要扫描的包。可以指定一个或多个包路径。

1
@ComponentScan(basePackages = {"com.example", "com.another"})

basePackageClasses属性

basePackageClasses属性用于指定一个或多个类,Spring 会扫描这些类所在的包。

1
@ComponentScan(basePackageClasses = {MyClass1.class, MyClass2.class})

includeFilters和excludeFilters属性

includeFilters和excludeFilters属性用于指定包含或排除的过滤器。可以根据注解、类型、正则表达式等进行过滤。

1
2
3
4
5
@ComponentScan(
basePackages = "com.example",
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com\\.example\\.exclude\\..*")
)

lazyInit属性

lazyInit属性用于指定是否延迟初始化扫描到的 Bean。默认值为false,表示立即初始化。

1
@ComponentScan(basePackages = "com.example", lazyInit = true)

示例

假设我们有以下包结构:

1
2
3
4
5
6
7
com.example
├── service
│ └── MyService.java
├── repository
│ └── MyRepository.java
└── controller
└── MyController.java

其中,MyService.java、MyRepository.java和MyController.java分别带有@Service、@Repository和@Controller注解。

定义配置类

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 配置类内容
}

启动 Spring 容器

1
2
3
4
5
6
7
8
9
10
11
12
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

MyService myService = context.getBean(MyService.class);
MyRepository myRepository = context.getBean(MyRepository.class);
MyController myController = context.getBean(MyController.class);

// 使用 Bean
context.close();
}
}

Spring 会扫描com.example包及其子包中的所有类,并将带有@Service、@Repository和@Controller注解的类注册为 Spring Bean。

👌Spring 的依赖注入是什么?

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Spring 的依赖注入是什么?

口语化答案

好的,面试官,依赖注入(Dependency Injection,简称DI)是Spring框架实现控制反转(IoC)的主要手段。DI的核心思想是将对象的依赖关系从对象内部抽离出来,通过外部注入的方式提供给对象。这样,依赖对象的创建和管理由Spring容器负责,而不是由对象自身负责,使得代码更加模块化、松耦合和易于测试。以上。

题目解析

重点题,三大概念之一。di,ioc,aop 之一,大家一定要整明白。

面试得分点

构造函数,setter,注解

详细答案

在传统编程中,一个对象通常会自己创建它所依赖的其他对象。这种方式使得代码紧密耦合,不利于维护和测试。依赖注入通过将依赖关系从代码中移除,转而由外部容器(如Spring容器)来注入,从而实现了对象之间的松耦合。

依赖注入的类型

Spring框架主要提供了三种依赖注入的方式:

构造函数注入:通过构造函数将依赖对象传递给被依赖对象。

1
2
3
4
5
6
7
public class Service {
private final Repository repository;

public Service(Repository repository) {
this.repository = repository;
}
}

Setter方法注入:通过Setter方法将依赖对象注入到被依赖对象中。

1
2
3
4
5
6
7
public class Service {
private Repository repository;

public void setRepository(Repository repository) {
this.repository = repository;
}
}

字段注入:直接在字段上使用注解进行注入。

1
2
3
4
public class Service {
@Autowired
private Repository repository;
}

依赖注入的配置方式

XML配置:通过XML文件定义Bean及其依赖关系。

1
2
3
4
5
6
<beans>
<bean id="repository" class="com.example.Repository"/>
<bean id="service" class="com.example.Service">
<constructor-arg ref="repository"/>
</bean>
</beans>

Java配置:通过Java类和注解定义Bean及其依赖关系。

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class AppConfig {
@Bean
public Repository repository() {
return new Repository();
}

@Bean
public Service service() {
return new Service(repository());
}
}

注解配置:通过注解(如@Component,@Autowired)自动扫描和注入Bean。

1
2
3
4
5
6
7
8
9
@Component
public class Repository {
}

@Component
public class Service {
@Autowired
private Repository repository;
}

Spring 的属性注入方式有哪几种

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌Spring 的属性注入方式有哪几种?

口语化答案

好的,面试官,属性注入的方式主要有几种,比如构造器注入,setter 注入,字段注入,value 注入等等。最常见的我们平时用的最多的就是,就是@Resource ,@Autowired 这种,像构造器和 setter 这种,经常在第三方依赖包的一些情况下,会进行使用,比如对接一些阿里云 oss,就体现的很明显。像@Value. 这种就常常用于读取配置文件的属性值,来使用。以上。

题目解析

常考题。也是年限低一点的小伙伴会问到,考察一下你对不同注入的情况,有没有一定的选择,以及了解当想注入一个属性的时候,都可以有什么方式的思路。

面试得分点

构造器,setter,@value。@resource @autowired。

题目详细答案

属性注入(Dependency Injection, DI)是将依赖对象注入到目标对象中的过程。Spring 支持多种属性注入方式,主要包括构造器注入、Setter 方法注入和字段注入。

构造器注入

构造器注入通过类的构造器来注入依赖对象。这种方式在对象创建时就完成了依赖注入,确保了依赖对象的不可变性。

1
2
3
4
5
6
7
public class MyBean {
private final Dependency dependency;

public MyBean(Dependency dependency) {
this.dependency = dependency;
}
}

XML 配置

1
2
3
4
<bean id="myBean" class="com.example.MyBean">
<constructor-arg ref="dependency"/>
</bean>
<bean id="dependency" class="com.example.Dependency"/>

注解配置

1
2
3
4
5
6
7
8
9
@Component
public class MyBean {
private final Dependency dependency;

@Autowired
public MyBean(Dependency dependency) {
this.dependency = dependency;
}
}

Setter 方法注入

Setter 方法注入通过类的 Setter 方法来注入依赖对象。这种方式允许在对象创建后进行依赖注入,并且可以在运行时更改依赖对象。

1
2
3
4
5
6
7
public class MyBean {
private Dependency dependency;

public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}

XML 配置

1
2
3
4
<bean id="myBean" class="com.example.MyBean">
<property name="dependency" ref="dependency"/>
</bean>
<bean id="dependency" class="com.example.Dependency"/>

注解配置

1
2
3
4
5
6
7
8
9
@Component
public class MyBean {
private Dependency dependency;

@Autowired
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}

字段注入

字段注入(也称为属性注入)直接通过在类的字段上使用注解来注入依赖对象。这种方式不需要 Setter 方法,但不推荐使用,因为它违反了面向对象设计原则,使得依赖关系不够明确。

1
2
3
4
5
@Component
public class MyBean {
@Autowired
private Dependency dependency;
}

通过@Value注解注入基本类型和字符串

除了注入 Bean 对象,Spring 还支持通过@Value注解注入基本类型、字符串和其他值,如从配置文件中读取的值。

1
2
3
4
5
@Component
public class MyBean {
@Value("${my.property}")
private String myProperty;
}

通过环境变量或系统属性注入

1
2
3
4
5
@Component
public class MyBean {
@Value("#{systemProperties['user.name']}")
private String userName;
}

通过@Resource注解注入

1
2
3
4
5
@Component
public class MyBean {
@Resource(name = "dependency")
private Dependency dependency;
}

通过@Inject注解注入

1
2
3
4
5
@Component
public class MyBean {
@Inject
private Dependency dependency;
}

👌bean 的作用范围和生命周期?

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌bean 的作用范围和生命周期?

口语化答案

好的,面试官。bean 的作用范围主要用 singleton,prototype,request,session,globalsession,application。常用的就是 singleton,singleton 是单例的,当 bean 是无状态的时候,singleton 是最好的使用方式,如果说 bean 里面涉及共享数据,singleton 就不够安全了,这个时候需要使用 prototype。bean 的生命周期从实例化创建 bean 开始,然后进行属性设置。再之后,调用 bean 的一些初始化方法,如果有则执行,这样处理完之后,bean 就可以被使用了。最终当 bean 要被销毁的时候,就会调用 destroy 方法进行 bean 的后置处理,以上。

题目解析

非常常考,一定要熟悉要 singleton 和 prototype 的区别。这个重点要的是状态问题。bean 的生命周期就比较好答,按照顺序来说就可以了。

面试得分点

singleton,prototype,无状态,实例化,属性设置,初始方法,销毁

题目详细答案

Bean的作用范围(Scope)和生命周期(Lifecycle)决定了Bean的创建、使用和销毁方式。

Bean的作用范围(Scope)

singleton:默认作用范围,整个Spring容器中只有一个实例,所有对该Bean的引用都指向同一个实例。适用于无状态的Bean。

1
<bean id="myBean" class="com.example.MyBean" scope="singleton"/>

prototype:每次请求该Bean时都会创建一个新的实例。适用于有状态的Bean。

1
<bean id="myBean" class="com.example.MyBean" scope="prototype"/>

request:每次HTTP请求都会创建一个新的实例,仅适用于Web应用。

1
<bean id="myBean" class="com.example.MyBean" scope="request"/>

session:每个HTTP会话都会创建一个新的实例,仅适用于Web应用。

1
<bean id="myBean" class="com.example.MyBean" scope="session"/>

globalSession:每个全局HTTP会话都会创建一个新的实例,仅适用于Portlet应用。

1
<bean id="myBean" class="com.example.MyBean" scope="globalSession"/>

application:每个ServletContext会创建一个新的实例,适用于Web应用。

1
<bean id="myBean" class="com.example.MyBean" scope="application"/>

Bean的生命周期

1724754790092-94de9a92-f85f-4b0f-851d-62174f065951.png

实例化(Instantiation)

Spring容器根据配置创建Bean实例。

属性设置(Property Population)

Spring容器进行依赖注入,设置Bean的属性。

初始化(Initialization)

如果Bean实现了InitializingBean接口,Spring会调用其afterPropertiesSet()方法。

如果在XML配置中指定了init-method属性,Spring会调用指定的初始化方法。

如果Bean使用了@PostConstruct注解,Spring会调用标注的方法。

使用(Usage)

Bean处于就绪状态,可以被应用程序使用。

销毁(Destruction)

如果Bean实现了DisposableBean接口,Spring会调用其destroy()方法。

如果在XML配置中指定了destroy-method属性,Spring会调用指定的销毁方法。

如果Bean使用了@PreDestroy注解,Spring会调用标注的方法。

生命周期回调接口和注解

InitializingBean接口

方法:afterPropertiesSet()

1
2
3
4
5
6
public class MyBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// 初始化逻辑
}
}

DisposableBean接口

方法:destroy()

1
2
3
4
5
6
public class MyBean implements DisposableBean {
@Override
public void destroy() throws Exception {
// 销毁逻辑
}
}

@PostConstruct注解

用于标注初始化方法。

1
2
3
4
5
6
public class MyBean {
@PostConstruct
public void init() {
// 初始化逻辑
}
}

@PreDestroy注解

用于标注销毁方法。

1
2
3
4
5
6
public class MyBean {
@PreDestroy
public void cleanup() {
// 销毁逻辑
}
}

代码 Demo

1
<bean id="myBean" class="com.example.MyBean" scope="singleton" init-method="customInit" destroy-method="customDestroy"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyBean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// 初始化逻辑
}

@Override
public void destroy() throws Exception {
// 销毁逻辑
}

@PostConstruct
public void init() {
// 初始化逻辑
}

@PreDestroy
public void cleanup() {
// 销毁逻辑
}

public void customInit() {
// 自定义初始化逻辑
}

public void customDestroy() {
// 自定义销毁逻辑
}
}

👌jdk动态代理如何实现

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌jdk动态代理如何实现

口语化答案

好的,面试官。jdk 的动态代理主要是依赖Proxy 和InvocationHandler 接口。jdk 动态代理要求类必须有接口。在进行实现的时候,首先要定义接口,比如MyService,这个接口就是我们的正常功能的实现。但是希望在不更改MyService 的情况下增加,那么我们需要定义一个实现InvocationHandler 接口的实现类,同时在方法实现上面增加额外的逻辑。最后通过 Proxy 的 newProxyInstance 将二者结合到一起。就实现了动态代理。以上。

题目解析

大家不要觉得动态代理很难理解,按照这个步骤其实你发现很简单。记忆的过程和 cglib 对比着看,就很轻松,面试也是属于常考一点的题目。

面试得分点

InvocationHandler 增强,Proxy 创建代理

题目详细答案

JDK 动态代理主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。

实现步骤

定义接口:定义需要代理的接口。

实现接口:创建接口的实现类。

创建调用处理器:实现InvocationHandler接口,并在invoke方法中定义代理逻辑。

创建代理对象:通过Proxy.newProxyInstance方法创建代理对象。

代码 Demo

假设我们有一个简单的服务接口MyService和它的实现类MyServiceImpl,我们将通过 JDK 动态代理为MyService创建一个代理对象,并在方法调用前后添加日志。

1. 定义接口

1
2
3
public interface MyService {
void performTask();
}

2. 实现接口

1
2
3
4
5
6
public class MyServiceImpl implements MyService {
@Override
public void performTask() {
System.out.println("Performing task");
}
}

3. 创建调用处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LoggingInvocationHandler implements InvocationHandler {
private final Object target;

public LoggingInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Logging before method execution: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("Logging after method execution: " + method.getName());
return result;
}
}

4. 创建代理对象并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.lang.reflect.Proxy;

public class MainApp {
public static void main(String[] args) {
// 创建目标对象
MyService myService = new MyServiceImpl();

// 创建调用处理器
LoggingInvocationHandler handler = new LoggingInvocationHandler(myService);

// 创建代理对象
MyService proxyInstance = (MyService) Proxy.newProxyInstance(
myService.getClass().getClassLoader(),
myService.getClass().getInterfaces(),
handler
);

// 调用代理对象的方法
proxyInstance.performTask();
}
}

详细解释

1、 接口定义和实现:

MyService是一个简单的接口,定义了一个方法performTask。

MyServiceImpl是MyService的实现类,实现了performTask方法。

2、 调用处理器:

LoggingInvocationHandler实现了InvocationHandler接口。它的invoke方法在代理对象的方法调用时被调用。invoke方法接收三个参数:proxy:代理对象。method:被调用的方法。args:方法参数。在invoke方法中,我们在方法调用前后添加了日志打印。

3、 创建代理对象:

使用Proxy.newProxyInstance方法创建代理对象。

newProxyInstance方法接收三个参数:类加载器:通常使用目标对象的类加载器。接口数组:目标对象实现的所有接口。调用处理器:实现了InvocationHandler接口的实例。

4、 使用代理对象:

通过代理对象调用方法时,实际调用的是LoggingInvocationHandler的invoke方法。

在invoke方法中,首先打印日志,然后通过反射调用目标对象的方法,最后再打印日志。

👌什么是aop,aop的作用和优势

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌什么是aop,aop的作用和优势?

口语化回答

好的,面试官。aop 是 spring 框架的一个核心。常见的可以做日志记录,事务管理等公共性的处理。当我们想要统一做一些水平统一的操作的时候,就常用 aop。aop 的核心概念就是面向切面变成。通过 aop 可以在范围内的类方法执行之前,执行之后,或者异常的时候,做统一的操作。aop 可以提高代码的可维护性,任何修改只需要在切面中进行,而不需要修改业务逻辑代码。基于动态代理机制,还可以在不修改源代码的情况下为现有代码添加功能。在 boot 应用使用中,只需要配合Aspect 注解,即可实现功能,以上。

题目解析

aop 是核心问题,spring 里面的比较好的一个特性,因为实际工作中非常常用,面试官就会问你,是否有用过这一块的东西。

面试得分点

面向切面,模块化,减少重复代码

题目详细答案

AOP(面向切面编程)是一种编程范式,它旨在通过分离横切关注点来提高代码的模块化。AOP 在传统的面向对象编程(OOP)基础上,提供了一种处理系统级关注点(如日志记录、事务管理、安全性等)的机制,而这些关注点通常会散布在多个模块中。

AOP 的基本概念

Aspect(切面):切面是模块化的横切关注点。它可以包含多个 advice(通知)和 pointcut(切入点)。

Advice(通知):通知是切面在特定的切入点执行的动作。通知有几种类型:

  • Before:在方法执行之前执行。
  • After:在方法执行之后执行。
  • AfterReturning:在方法成功执行之后执行。
  • AfterThrowing:在方法抛出异常后执行。
  • Around:包围一个方法的执行,能够控制方法的执行前后。

Pointcut(切入点):切入点定义了通知应该应用到哪些连接点上。连接点是程序执行的特定点,如方法调用或异常抛出。

Join Point(连接点):程序执行的特定点,例如方法调用或异常抛出。

Weaving(织入):将切面应用到目标对象创建代理对象的过程。织入可以在编译时、类加载时、运行时进行。

AOP 的作用和优势

模块化横切关注点:AOP 允许将横切关注点(如日志记录、事务管理、安全性等)从业务逻辑中分离出来,从而提高代码的模块化和可维护性。

减少重复代码:通过将通用功能提取到切面中,可以减少代码的重复,提高代码的可读性和可维护性。

提高代码的可维护性:由于横切关注点被模块化为切面,任何修改只需要在切面中进行,而不需要修改业务逻辑代码,从而提高了代码的可维护性。

动态代理:AOP 使用动态代理机制,可以在不修改源代码的情况下为现有代码添加功能。

增强代码的可测试性:由于横切关注点被分离成切面,业务逻辑代码变得更加简洁和专注,从而提高了代码的可测试性。

AOP 的应用场景

日志记录:在方法调用前后记录日志。

事务管理:在方法执行前开启事务,在方法执行后提交事务,在方法抛出异常时回滚事务。

安全性检查:在方法执行前进行权限检查。

性能监控:在方法执行前后记录执行时间。

缓存:在方法调用前检查缓存,在方法调用后更新缓存。

示例代码

定义切面

1
2
3
4
5
6
7
8
9
10
11
12
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Logging before method execution");
}
}

配置 Spring 应用

1
2
3
4
5
6
7
8
9
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = "com.example")
@EnableAspectJAutoProxy
public class AppConfig {
}

使用 AOP 的服务类

1
2
3
4
5
6
7
8
import org.springframework.stereotype.Service;

@Service
public class MyService {
public void performTask() {
System.out.println("Performing task");
}
}

主应用

1
2
3
4
5
6
7
8
9
10
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = context.getBean(MyService.class);
myService.performTask();
}
}

LoggingAspect切面会在每次调用MyService中的方法时记录日志,而无需修改MyService的源代码。 AOP 通过分离横切关注点来提高代码的模块化和可维护性。

<i class="fa fa-angle-left"></i>1…567…12<i class="fa fa-angle-right"></i>

232 日志
18 分类
28 标签
GitHub
© 2025 javayun
由 Hexo 强力驱动
主题 - NexT.Gemini