Spring

 

Spring

1.Spring是什么?

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

到了现在,企业级开发的标配基本就是 Spring5 + Spring Boot 2 + JDK 8

Spring的特性

IoC 和 DI 的支持 Spring 的核心就是一个大的工厂容器,可以维护所有对象的创建和依赖关系,Spring 工厂用于生成 Bean,并且管理 Bean 的生命周期,实现高内聚低耦合的设计理念。

AOP 编程的支持 Spring 提供了面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等切面功能。

声明式事务的支持 支持通过配置就来完成对事务的管理,而不需要通过硬编码的方式,以前重复的一些事务提交、回滚的 JDBC 代码,都可以不用自己写了。

快捷测试的支持 Spring 对 Junit 提供支持,可以通过注解快捷地测试 Spring 程序。

快速集成功能 方便集成各种优秀框架,Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持。

复杂 API 模板封装 Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了模板化的封装,这些封装 API 的提供使得应用难度大大降低。

2.Spring的模块

Spring 框架是一个庞大的生态系统,包含多个模块,每个模块都提供了特定的功能,以满足不同的开发需求。以下是 Spring 框架的主要模块:

1. 核心容器(Core Container)

  • Spring Core:提供框架的基本功能,包括依赖注入(DI)和控制反转(IoC)。
  • Spring Beans:提供 Bean 的创建、配置和管理功能。
  • Spring Context:提供框架的上下文信息,扩展了 BeanFactory,支持国际化、事件传播等功能。
  • Spring Expression Language (SpEL):提供强大的表达式语言,用于在运行时查询和操作对象图。

2. 数据访问/集成(Data Access/Integration)

  • Spring JDBC:简化了 JDBC 操作,提供了模板类来减少冗余代码。
  • Spring ORM:支持集成 ORM 框架,如 Hibernate、JPA、MyBatis 等。
  • Spring OXM:提供了对象/XML 映射的支持。
  • Spring JMS:简化了 JMS(Java Message Service)的使用。
  • Spring Transaction:提供声明式事务管理,简化了事务处理。

3. Web 模块(Web)

  • Spring Web:提供了基础的 Web 开发功能,包括多部分文件上传、初始化参数等。
  • Spring WebMVC:基于模型-视图-控制器(MVC)设计模式的 Web 框架,用于构建 Web 应用程序。
  • Spring WebSocket:提供了 WebSocket 的支持,用于构建实时通信应用。
  • Spring WebFlux:提供了响应式编程模型,用于构建非阻塞的 Web 应用程序。

4. 面向切面编程(Aspect-Oriented Programming, AOP)

  • Spring AOP:提供了面向切面编程的支持,可以实现横切关注点(如日志记录、事务管理等)的分离。
  • Spring Aspects:提供了与 AspectJ 的集成,增强了 AOP 的功能。

5. 消息(Messaging)

  • Spring Messaging:提供了消息传递的支持,简化了消息驱动的应用程序开发。

6. 测试(Test)

  • Spring Test:提供了对 JUnit 和 TestNG 的支持,简化了 Spring 应用程序的测试。

7. 安全(Security)

  • Spring Security:提供了强大的认证和授权功能,保护应用程序的安全。

8. 云(Cloud)

  • Spring Cloud:提供了构建分布式系统的工具和服务,如配置管理、服务发现、断路器等。

9. 批处理(Batch)

  • Spring Batch:提供了批处理应用程序的支持,简化了大规模数据处理任务的开发。

10. 数据流(Data Flow)

  • Spring Data Flow:提供了数据流处理的支持,简化了数据流应用程序的开发和管理。

总结-1

Spring 框架包含多个模块,每个模块都提供了特定的功能,以满足不同的开发需求。以下是主要模块:

  • 核心容器(Core Container):Spring Core、Spring Beans、Spring Context、SpEL。
  • 数据访问/集成(Data Access/Integration):Spring JDBC、Spring ORM、Spring OXM、Spring JMS、Spring Transaction。
  • Web 模块(Web):Spring Web、Spring WebMVC、Spring WebSocket、Spring WebFlux。
  • 面向切面编程(AOP):Spring AOP、Spring Aspects。
  • 消息(Messaging):Spring Messaging。
  • 测试(Test):Spring Test。
  • 安全(Security):Spring Security。
  • 云(Cloud):Spring Cloud。
  • 批处理(Batch):Spring Batch。
  • 数据流(Data Flow):Spring Data Flow。

通过理解 Spring 框架的各个模块,可以更好地利用其提供的功能,构建高效、可扩展的企业级应用程序。

3.Spring有哪些常用注解?

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

①、@Controller:用于标注控制层组件。

②、@RestController:是@Controller 和 @ResponseBody 的结合体,返回 JSON 数据时使用。

③、@RequestMapping:用于映射请求 URL 到具体的方法上,还可以细分为:

  • @GetMapping:只能用于处理 GET 请求
  • @PostMapping:只能用于处理 POST 请求
  • @DeleteMapping:只能用于处理 DELETE 请求

④、@ResponseBody:直接将返回的数据放入 HTTP 响应正文中,一般用于返回 JSON 数据。

⑤、@RequestBody:表示一个方法参数应该绑定到 Web 请求体。

⑥、@PathVariable:用于接收路径参数,比如 @RequestMapping(“/hello/{name}”),这里的 name 就是路径参数。

⑦、@RequestParam:用于接收请求参数。比如 @RequestParam(name = “key”) String key,这里的 key 就是请求参数。

容器类注解有哪些呢?

@Component:标识一个类为 Spring 组件,使其能够被 Spring 容器自动扫描和管理。

@Service:标识一个业务逻辑组件(服务层)。比如@Service(“userService”),这里的 userService 就是 Bean 的名称。

@Repository:标识一个数据访问组件(持久层)。

@Autowired:按类型自动注入依赖。

@Configuration:用于定义配置类,可替换 XML 配置文件。

@Value:用于将 Spring Boot 中 application.properties 配置的属性值赋值给变量。

AOP 方面有哪些注解呢?

@Aspect 用于声明一个切面,可以配合其他注解一起使用,比如:

@After:在方法执行之后执行。

@Before:在方法执行之前执行。

@Around:方法前后均执行。

@PointCut:定义切点,指定需要拦截的方法。

事务注解有哪些?

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

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

①、工厂模式:IoC 容器本身可以看作是一个巨大的工厂,负责创建和管理 Bean 的生命周期和依赖关系。

像 BeanFactory 和 ApplicationContext 接口都提供了工厂模式的实现,负责实例化、配置和组装 Bean。

②、代理模式:AOP 的实现就是基于代理模式的,如果配置了事务管理,Spring 会使用代理模式创建一个连接数据库的代理对象,来进行事务管理。

③、单例模式:Spring 容器中的 Bean 默认都是单例的,这样可以保证 Bean 的唯一性,减少系统开销。

④、模板模式:Spring 中的 JdbcTemplate,HibernateTemplate 等以 Template 结尾的类,都使用了模板方法模式。

比如,我们使用 JdbcTemplate,只需要提供 SQL 语句和需要的参数就可以了,至于如何创建连接、执行 SQL、处理结果集等都由 JdbcTemplate 这个模板方法来完成。

④、观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用,Spring 中的 ApplicationListener 就是观察者,当有事件(ApplicationEvent)被发布,ApplicationListener 就能接收到信息。

⑤、适配器模式:Spring MVC 中的 HandlerAdapter 就用了适配器模式。它允许 DispatcherServlet 通过统一的适配器接口与多种类型的请求处理器进行交互。

⑥、策略模式:Spring 中有一个 Resource 接口,它的不同实现类,会根据不同的策略去访问资源。

5.spring的容器、web容器、springmvc的容器之间的区别?

Spring 容器、Web 容器和 Spring MVC 容器是 Java 开发中常见的三种容器,它们在功能和用途上有所不同。以下是它们的区别和各自的特点:

1. Spring 容器

描述:Spring 容器是 Spring 框架的核心部分,负责管理应用程序中的 Bean 的生命周期和依赖关系。

主要功能

  • 依赖注入(DI):通过配置文件或注解,将对象的依赖关系注入到对象中。
  • Bean 生命周期管理:管理 Bean 的创建、初始化、销毁等生命周期。
  • AOP 支持:提供面向切面编程的支持,允许在运行时动态地为对象添加行为。

常见实现

  • BeanFactory:最基本的容器,提供基本的 DI 功能。
  • ApplicationContext:扩展了 BeanFactory,提供更多的企业级功能,如事件传播、国际化、应用上下文等。

示例

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = context.getBean(MyBean.class);

2. Web 容器

描述:Web 容器(也称为 Servlet 容器)是用于管理和执行 Java Web 应用程序的容器,负责处理 HTTP 请求和响应。

主要功能

  • Servlet 管理:加载、初始化、执行和销毁 Servlet。
  • 会话管理:管理用户会话,跟踪用户状态。
  • 安全管理:提供认证和授权机制,保护 Web 应用程序的安全。
  • 请求调度:将 HTTP 请求分发到相应的 Servlet 进行处理。

常见实现

  • Apache Tomcat:一个流行的开源 Web 容器。
  • Jetty:一个轻量级的 Web 容器,适用于嵌入式应用。
  • WildFly(原 JBoss):一个功能强大的企业级应用服务器。

示例

<servlet>
    <servlet-name>example</servlet-name>
    <servlet-class>com.example.ExampleServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>example</servlet-name>
    <url-pattern>/example</url-pattern>
</servlet-mapping>

3. Spring MVC 容器

描述:Spring MVC 容器是 Spring 框架的一部分,专门用于处理 Web 请求和响应,基于模型-视图-控制器(MVC)设计模式。

主要功能

  • 请求映射:将 HTTP 请求映射到控制器方法。
  • 数据绑定:将请求参数绑定到方法参数或模型对象。
  • 视图解析:将控制器返回的模型数据渲染到视图(如 JSP、Thymeleaf)。
  • 表单处理:处理表单提交和验证。

常见组件

  • DispatcherServlet:前端控制器,负责将请求分发到相应的处理器。
  • Controller:处理请求并返回模型数据和视图名称。
  • ViewResolver:解析视图名称并渲染视图。

示例

@Controller
@RequestMapping("/users")
public class UserController {
    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id, Model model) {
        User user = userService.findById(id);
        model.addAttribute("user", user);
        return "user";
    }
}

总结-5

  • Spring 容器
    • 功能:管理 Bean 的生命周期和依赖关系,提供 AOP 支持。
    • 实现:BeanFactory、ApplicationContext。
    • 用途:用于管理应用程序中的对象和依赖关系。
  • Web 容器
    • 功能:管理和执行 Java Web 应用程序,处理 HTTP 请求和响应。
    • 实现:Apache Tomcat、Jetty、WildFly。
    • 用途:用于部署和运行 Web 应用程序。
  • Spring MVC 容器
    • 功能:处理 Web 请求和响应,基于 MVC 设计模式。
    • 组件:DispatcherServlet、Controller、ViewResolver。
    • 用途:用于构建基于 Spring 框架的 Web 应用程序。

通过理解这三种容器的区别和各自的功能,可以更好地利用它们来构建高效、可扩展的 Java 应用程序。

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

控制反转(Inversion of Control, IoC)

描述:控制反转是一种设计原则,用于将对象的创建和依赖关系的管理从应用程序代码中分离出来,交给外部容器(如 Spring 容器)来处理。通过 IoC,应用程序不再负责创建和管理对象,而是由容器来控制对象的生命周期和依赖关系。

主要思想

  • 反转控制:传统的程序设计中,对象是由应用程序代码主动创建和管理的,而在 IoC 中,这种控制权被反转,交给了容器。
  • 松耦合:通过 IoC,可以实现对象之间的松耦合,增强代码的可维护性和可测试性。

实现方式

  • 依赖注入(DI):IoC 的一种具体实现方式,通过注入依赖对象来实现控制反转。
  • 依赖查找(DL):另一种实现方式,通过容器提供的查找方法获取依赖对象。

依赖注入(Dependency Injection, DI)

描述:依赖注入是实现 IoC 的一种方式,通过将对象的依赖关系注入到对象中,而不是在对象内部创建依赖对象。DI 可以通过构造器注入、Setter 注入和字段注入等方式实现。

主要方式

  • 构造器注入:通过构造器参数注入依赖对象。
  • Setter 注入:通过 Setter 方法注入依赖对象。
  • 字段注入:通过注解直接注入依赖对象。

示例

1. 构造器注入:

public class Service {
    private final Repository repository;

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

2. Setter 注入:

public class Service {
    private Repository repository;

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

3. 字段注入:

public class Service {
    @Autowired
    private Repository repository;
}

IoC 和 DI 的关系

  • IoC 是一种设计原则:控制反转是一种设计原则,用于将对象的创建和管理交给外部容器。
  • DI 是 IoC 的一种实现方式:依赖注入是实现控制反转的一种具体方式,通过注入依赖对象来实现对象之间的松耦合。

Spring 中的 IoC 和 DI

Spring 框架广泛应用了 IoC 和 DI 原则,通过 Spring 容器来管理对象的创建和依赖关系,实现对象之间的松耦合。

Spring 容器

  • BeanFactory:最基本的 IoC 容器,提供基本的 DI 功能。
  • ApplicationContext:扩展了 BeanFactory,提供更多的企业级功能,如事件传播、国际化、应用上下文等。

Spring 中的 DI

  • 注解:如 @Autowired@Qualifier@Resource 等。
  • XML 配置:通过 XML 文件配置依赖关系。
  • Java 配置:通过 Java 配置类和 @Bean 注解配置依赖关系。

示例

@Configuration
public class AppConfig {
    @Bean
    public Repository repository() {
        return new Repository();
    }

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

总结-6

  • 控制反转(IoC):一种设计原则,将对象的创建和管理交给外部容器,实现对象之间的松耦合。
  • 依赖注入(DI):实现 IoC 的一种方式,通过注入依赖对象来实现控制反转。
    • 构造器注入:通过构造器参数注入依赖对象。
    • Setter 注入:通过 Setter 方法注入依赖对象。
    • 字段注入:通过注解直接注入依赖对象。

通过理解 IoC 和 DI 的概念及其在 Spring 中的应用,可以更好地利用 Spring 框架提供的功能,编写高质量的代码。

7.能简单说一下 Spring IoC 的实现机制吗?

Spring 的 IoC 容器通过依赖注入(DI)来管理对象的创建和依赖关系,实现对象之间的松耦合。以下是 Spring IoC 的实现机制的关键步骤:

1. 配置元数据

Spring IoC 容器需要配置元数据来定义 Bean 及其依赖关系。配置元数据可以通过以下几种方式提供:

  • XML 配置:在 XML 文件中定义 Bean 和依赖关系。
  • 注解:使用注解(如 @Component@Autowired 等)来标记和注入 Bean。
  • Java 配置:使用 Java 配置类和 @Bean 注解来定义 Bean。

2. 解析配置元数据

Spring IoC 容器在启动时会解析配置元数据,构建内部的数据结构来表示 Bean 及其依赖关系。

  • XML 配置解析:解析 XML 文件中的 <bean> 元素。
  • 注解解析:扫描类路径中的注解(如 @Component@Service 等)。
  • Java 配置解析:解析配置类中的 @Bean 方法。

3. 创建 Bean 实例

Spring IoC 容器根据解析后的配置元数据创建 Bean 实例。创建 Bean 实例的过程包括以下几个步骤:

  • 实例化:使用反射机制调用构造器创建 Bean 实例。
  • 依赖注入:注入 Bean 的依赖对象,可以通过构造器注入、Setter 注入或字段注入实现。
  • 初始化:调用 Bean 的初始化方法(如 @PostConstruct 注解的方法或实现 InitializingBean 接口的 afterPropertiesSet 方法)。

4. 管理 Bean 生命周期

Spring IoC 容器管理 Bean 的整个生命周期,包括创建、初始化、销毁等阶段。

  • 单例 Bean:默认情况下,Spring 容器中的 Bean 是单例的,即每个 Bean 只有一个实例。
  • 原型 Bean:每次请求都会创建一个新的 Bean 实例。
  • 自定义 Bean 生命周期:可以通过实现 BeanPostProcessor 接口来自定义 Bean 的初始化和销毁逻辑。

5. 提供 Bean

Spring IoC 容器通过 BeanFactoryApplicationContext 提供对 Bean 的访问。应用程序可以通过这些接口获取和使用 Bean。

  • BeanFactory:最基本的 IoC 容器,提供基本的 DI 功能。
  • ApplicationContext:扩展了 BeanFactory,提供更多的企业级功能,如事件传播、国际化、应用上下文等。

XML 配置

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="repository" class="com.example.Repository"/>
    <bean id="service" class="com.example.Service">
        <constructor-arg ref="repository"/>
    </bean>
</beans>

注解配置

@Component
public class Repository {
    // Repository implementation
}

@Service
public class Service {
    private final Repository repository;

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

Java 配置

@Configuration
public class AppConfig {
    @Bean
    public Repository repository() {
        return new Repository();
    }

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

总结-7

  • 配置元数据:通过 XML 配置、注解或 Java 配置定义 Bean 和依赖关系。
  • 解析配置元数据:Spring IoC 容器解析配置元数据,构建内部数据结构。
  • 创建 Bean 实例:使用反射机制创建 Bean 实例,并注入依赖对象。
  • 管理 Bean 生命周期:管理 Bean 的创建、初始化和销毁等生命周期阶段。
  • 提供 Bean:通过 BeanFactoryApplicationContext 提供对 Bean 的访问。

8.说说 BeanFactory 和 ApplicantContext?

BeanFactoryApplicationContext 是 Spring 框架中用于管理 Bean 的两种主要容器接口。它们在功能和使用场景上有所不同。

1. BeanFactory

描述BeanFactory 是 Spring 的基础 IoC 容器,提供了基本的依赖注入功能。它是 Spring 容器的核心接口,定义了 Bean 的创建、配置和管理方式。

主要特点

  • 延迟加载BeanFactory 采用延迟加载(Lazy Loading)策略,只有在第一次访问 Bean 时才会创建该 Bean 实例。
  • 轻量级BeanFactory 是一个轻量级容器,适用于资源受限的环境。

常见实现

  • XmlBeanFactory:从 XML 配置文件中读取 Bean 定义并创建 Bean 实例(已废弃,推荐使用 ClassPathXmlApplicationContextFileSystemXmlApplicationContext)。

示例

BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = factory.getBean(MyBean.class);

2. ApplicationContext

描述ApplicationContextBeanFactory 的子接口,扩展了 BeanFactory 的功能,提供了更多的企业级特性。它是 Spring 框架中最常用的容器接口。

主要特点

  • 立即加载ApplicationContext 采用立即加载(Eager Loading)策略,在容器启动时就会创建所有单例 Bean 实例。
  • 国际化支持:提供了国际化(i18n)支持,可以方便地处理多语言应用。
  • 事件机制:支持事件发布和监听机制,可以在应用程序中发布和监听事件。
  • AOP 支持:集成了 Spring AOP,提供了面向切面编程的支持。
  • 注解支持:支持基于注解的配置和依赖注入。

常见实现

  • ClassPathXmlApplicationContext:从类路径下的 XML 配置文件中读取 Bean 定义并创建 Bean 实例。
  • FileSystemXmlApplicationContext:从文件系统中的 XML 配置文件中读取 Bean 定义并创建 Bean 实例。
  • AnnotationConfigApplicationContext:从 Java 配置类中读取 Bean 定义并创建 Bean 实例。

示例

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = context.getBean(MyBean.class);

主要区别

特性 BeanFactory ApplicationContext
加载策略 延迟加载(Lazy Loading) 立即加载(Eager Loading)
国际化支持 不支持 支持
事件机制 不支持 支持
AOP 支持 基本支持 完整支持
注解支持 基本支持 完整支持
使用场景 资源受限的环境 企业级应用程序

总结- 8

  • BeanFactory
    • 描述:Spring 的基础 IoC 容器,提供基本的依赖注入功能。
    • 特点:延迟加载、轻量级。
    • 使用场景:资源受限的环境。
    • 示例

      BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
      MyBean myBean = factory.getBean(MyBean.class);
      
  • ApplicationContext
    • 描述BeanFactory 的子接口,扩展了更多企业级特性。
    • 特点:立即加载、国际化支持、事件机制、AOP 支持、注解支持。
    • 使用场景:企业级应用程序。
    • 示例

      ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
      MyBean myBean = context.getBean(MyBean.class);
      

通过理解 BeanFactoryApplicationContext 的区别和各自的特点,可以更好地选择和使用 Spring 容器来管理应用程序中的 Bean。

9.Spring 的 Bean 实例化方式?

Spring 框架提供了多种方式来实例化 Bean,主要包括以下几种:

1. 构造器实例化

描述:通过调用类的构造器来创建 Bean 实例。这是最常见的实例化方式。

配置方式

  • XML 配置:使用 <constructor-arg> 元素指定构造器参数。
  • 注解配置:使用 @Autowired 注解在构造器上进行自动注入。
  • Java 配置:在 @Bean 方法中直接调用构造器。

示例

  • XML 配置
<bean id="myBean" class="com.example.MyBean">
    <constructor-arg value="constructorArgValue"/>
</bean>
  • 注解配置
@Component
public class MyBean {
    private final String value;

    @Autowired
    public MyBean(@Value("constructorArgValue") String value) {
        this.value = value;
    }
}
  • Java 配置
@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        return new MyBean("constructorArgValue");
    }
}

2. 静态工厂方法实例化

描述:通过调用静态工厂方法来创建 Bean 实例。静态工厂方法是一个静态方法,它返回一个 Bean 实例。

配置方式

  • XML 配置:使用 factory-method 属性指定静态工厂方法。
  • Java 配置:在 @Bean 方法中调用静态工厂方法。

示例

  • XML 配置
<bean id="myBean" class="com.example.MyBeanFactory" factory-method="createInstance"/>
  • Java 配置
@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        return MyBeanFactory.createInstance();
    }
}

3. 实例工厂方法实例化

描述:通过调用实例工厂方法来创建 Bean 实例。实例工厂方法是一个实例方法,它返回一个 Bean 实例。

配置方式

  • XML 配置:使用 factory-beanfactory-method 属性指定实例工厂和工厂方法。
  • Java 配置:在 @Bean 方法中调用实例工厂方法。

示例

  • XML 配置
<bean id="myBeanFactory" class="com.example.MyBeanFactory"/>
<bean id="myBean" factory-bean="myBeanFactory" factory-method="createInstance"/>
  • Java 配置
@Configuration
public class AppConfig {
    @Bean
    public MyBeanFactory myBeanFactory() {
        return new MyBeanFactory();
    }

    @Bean
    public MyBean myBean() {
        return myBeanFactory().createInstance();
    }
}

总结-9

Spring 提供了多种方式来实例化 Bean,主要包括:

  1. 构造器实例化:通过调用类的构造器来创建 Bean 实例。
    • XML 配置:使用 <constructor-arg> 元素。
    • 注解配置:使用 @Autowired 注解在构造器上。
    • Java 配置:在 @Bean 方法中直接调用构造器。
  2. 静态工厂方法实例化:通过调用静态工厂方法来创建 Bean 实例。
    • XML 配置:使用 factory-method 属性。
    • Java 配置:在 @Bean 方法中调用静态工厂方法。
  3. 实例工厂方法实例化:通过调用实例工厂方法来创建 Bean 实例。
    • XML 配置:使用 factory-beanfactory-method 属性。
    • Java 配置:在 @Bean 方法中调用实例工厂方法。

10.能说一下 Spring Bean 生命周期吗?

Spring 中 Bean 的生命周期大致分为四个阶段:实例化(Instantiation)、属性赋值(Populate)、初始化(Initialization)、销毁(Destruction)。

实例化:Spring 容器根据 Bean 的定义创建 Bean 的实例,相当于执行构造方法,也就是 new 一个对象。

属性赋值:相当于执行 setter 方法为字段赋值。

初始化:初始化阶段允许执行自定义的逻辑,比如设置某些必要的属性值、开启资源、执行预加载操作等,以确保 Bean 在使用之前是完全配置好的。

销毁:相当于执行 = null,释放资源。

11.Bean 定义和依赖定义有哪些方式?

有三种方式:直接编码方式、配置文件方式、注解方式。

  • 直接编码方式:我们一般接触不到直接编码的方式,但其实其它的方式最终都要通过直接编码来实现。
  • 配置文件方式:通过 xml、propreties 类型的配置文件,配置相应的依赖关系,Spring 读取配置文件,完成依赖关系的注入。
  • 注解方式:注解方式应该是我们用的最多的一种方式了,在相应的地方使用注解修饰,Spring 会扫描注解,完成依赖关系的注入。

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

Spring IoC 容器知道所有 Bean 的配置信息,此外,通过 Java 反射机制还可以获知实现类的结构信息,如构造方法的结构、属性等信息。掌握所有 Bean 的这些信息后,Spring IoC 容器就可以按照某种规则对容器中的 Bean 进行自动装配,而无须通过显式的方式进行依赖配置。

Spring 提供的这种方式,可以按照某些规则进行 Bean 的自动装配,<bean>元素提供了一个指定自动装配类型的属性:autowire="<自动装配类型>"

  • byName:根据名称进行自动匹配,假设 Boss 又一个名为 car 的属性,如果容器中刚好有一个名为 car 的 bean,Spring 就会自动将其装配给 Boss 的 car 属性
  • byType:根据类型进行自动匹配,假设 Boss 有一个 Car 类型的属性,如果容器中刚好有一个 Car 类型的 Bean,Spring 就会自动将其装配给 Boss 这个属性
  • constructor:与 byType 类似, 只不过它是针对构造函数注入而言的。如果 Boss 有一个构造函数,构造函数包含一个 Car 类型的入参,如果容器中有一个 Car 类型的 Bean,则 Spring 将自动把这个 Bean 作为 Boss 构造函数的入参;如果容器中没有找到和构造函数入参匹配类型的 Bean,则 Spring 将抛出异常。
  • autodetect:根据 Bean 的自省机制决定采用 byType 还是 constructor 进行自动装配,如果 Bean 提供了默认的构造函数,则采用 byType,否则采用 constructor。

13.Spring 中的 Bean 的作用域有哪些?

Spring 的 Bean 主要支持五种作用域:

  • singleton : 在 Spring 容器仅存在一个 Bean 实例,Bean 以单实例的方式存在,是 Bean 默认的作用域。
  • prototype : 每次从容器重调用 Bean 时,都会返回一个新的实例。

以下三个作用域于只在 Web 应用中适用:

  • request : 每一次 HTTP 请求都会产生一个新的 Bean,该 Bean 仅在当前 HTTP Request 内有效。
  • session : 同一个 HTTP Session 共享一个 Bean,不同的 HTTP Session 使用不同的 Bean。
  • globalSession:同一个全局 Session 共享一个 Bean,只用于基于 Protlet 的 Web 应用,Spring5 中已经不存在了。

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

是的,Spring 中的单例(Singleton)Bean 可能会存在线程安全问题。

单例 Bean 的线程安全问题

描述:在 Spring 中,单例作用域(Singleton Scope)意味着在整个 Spring 容器中只有一个 Bean 实例。这个实例会被多个线程共享使用。如果这个单例 Bean 中包含了可变状态(即成员变量可以被修改),并且这些状态在多个线程之间共享,那么就可能会出现线程安全问题。

线程安全问题的示例

假设有一个单例 Bean Counter,它包含一个可变的计数器变量:

@Component
public class Counter {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

在多线程环境中,如果多个线程同时调用 increment 方法,由于 count 变量的读写操作不是原子的,可能会导致计数器的值不正确。

解决线程安全问题的方法

1. 无状态 Bean:尽量设计无状态的 Bean,即不包含可变的成员变量。无状态的 Bean 是线程安全的,因为它们不在多个线程之间共享状态。

2. 局部变量:将可变状态限制在方法内部,使用局部变量而不是成员变量。局部变量是线程安全的,因为它们是线程私有的。

3. 同步:对共享的可变状态进行同步,确保同一时刻只有一个线程可以访问该状态。

@Component
public class Counter {
    private int count = 0;

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

    public synchronized int getCount() {
        return count;
    }
}

4. 使用线程安全的数据结构:使用 Java 提供的线程安全的数据结构,如 AtomicIntegerConcurrentHashMap 等。

@Component
public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}

5. 作用域调整:如果 Bean 的状态必须是可变的,并且需要在不同线程之间共享,可以考虑将 Bean 的作用域调整为 prototype,这样每次请求都会创建一个新的 Bean 实例。

@Component
@Scope("prototype")
public class Counter {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

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

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

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

总结- 14

Spring 中的单例(Singleton)Bean 可能会存在线程安全问题,特别是当 Bean 包含可变状态时。在多线程环境中,多个线程共享同一个单例 Bean 实例,可能会导致数据不一致或其他并发问题。

解决线程安全问题的方法包括:

  1. 设计无状态的 Bean。
  2. 使用局部变量而不是成员变量。
  3. 对共享的可变状态进行同步。
  4. 使用线程安全的数据结构。
  5. 将 Bean 的作用域调整为 prototype
  6. 将 Bean 中的成员变量保存到 ThreadLocal 中

通过理解和应用这些方法,可以有效地避免单例 Bean 的线程安全问题,确保应用程序的正确性和稳定性。

15.循环依赖问题

循环依赖(Circular Dependency)是指两个或多个 Bean 互相依赖,形成一个闭环。例如,Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A。这种依赖关系会导致在创建 Bean 实例时出现问题,因为 Spring 容器无法确定哪个 Bean 应该先创建。

循环依赖只发生在 Singleton 作用域的 Bean 之间,因为如果是 Prototype 作用域的 Bean,Spring 会直接抛出异常。

原因很简单,AB 循环依赖,A 实例化的时候,发现依赖 B,创建 B 实例,创建 B 的时候发现需要 A,创建 A1 实例……无限套娃。

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

  • AB 均采用构造器注入,不支持
  • AB 均采用 setter 注入,支持
  • AB 均采用属性自动注入,支持
  • A 中注入的 B 为 setter 注入,B 中注入的 A 为构造器注入,支持
  • B 中注入的 A 为 setter 注入,A 中注入的 B 为构造器注入,不支持

第四种可以,第五种不可以的原因是 Spring 在创建 Bean 时默认会根据自然排序进行创建,所以 A 会先于 B 进行创建。

当循环依赖的实例都采用 setter 方法注入时,Spring 支持,都采用构造器注入的时候,不支持。

Spring 怎么解决循环依赖的呢?

Spring 通过三级缓存(Three-Level Cache)机制来解决循环依赖。

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

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

A 实例的初始化过程:

①、创建 A 实例,实例化的时候把 A 的对象⼯⼚放⼊三级缓存,表示 A 开始实例化了,虽然这个对象还不完整,但是先曝光出来让大家知道。

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

③、同样,B 注⼊属性时发现依赖 A,它就从缓存里找 A 对象。依次从⼀级到三级缓存查询 A。

发现可以从三级缓存中通过对象⼯⼚拿到 A,虽然 A 不太完善,但是存在,就把 A 放⼊⼆级缓存,同时删除三级缓存中的 A,此时,B 已经实例化并且初始化完成了,把 B 放入⼀级缓存。

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

⑤、最后,⼀级缓存中保存着实例化、初始化都完成的 A、B 对象。

为什么要三级缓存?二级不行吗?

代理对象的提前暴露

在某些情况下,Spring 需要为 Bean 创建代理对象(例如,AOP 切面)。如果仅使用二级缓存,Spring 无法在 Bean 实例化的早期阶段创建代理对象并将其暴露出来。而三级缓存允许 Spring 在 Bean 实例化的早期阶段通过 Bean 工厂对象创建代理对象,并将其暴露出来,从而解决代理对象的提前暴露问题。

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

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

16.@Autowired 的实现原理?

实现@Autowired 的关键是:AutowiredAnnotationBeanPostProcessor

在 Bean 的初始化阶段,会通过 Bean 后置处理器来进行一些前置和后置的处理。

实现@Autowired 的功能,也是通过后置处理器来完成的。这个后置处理器就是 AutowiredAnnotationBeanPostProcessor。

Spring 在创建 bean 的过程中,最终会调用到 doCreateBean()方法,在 doCreateBean()方法中会调用 populateBean()方法,来为 bean 进行属性填充,完成自动装配等工作。

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

/**
* 属性赋值
**/
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
          //…………
          if (hasInstAwareBpps) {
              if (pvs == null) {
                  pvs = mbd.getPropertyValues();
              }

              PropertyValues pvsToUse;
              for(Iterator var9 = this.getBeanPostProcessorCache().instantiationAware.iterator(); var9.hasNext(); pvs = pvsToUse) {
                  InstantiationAwareBeanPostProcessor bp = (InstantiationAwareBeanPostProcessor)var9.next();
                  pvsToUse = bp.postProcessProperties((PropertyValues)pvs, bw.getWrappedInstance(), beanName);
                  if (pvsToUse == null) {
                      if (filteredPds == null) {
                          filteredPds = this.filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                      }
                      //执行后处理器,填充属性,完成自动装配
                      //调用InstantiationAwareBeanPostProcessor的postProcessPropertyValues()方法
                      pvsToUse = bp.postProcessPropertyValues((PropertyValues)pvs, filteredPds, bw.getWrappedInstance(), beanName);
                      if (pvsToUse == null) {
                          return;
                      }
                  }
              }
          }
         //…………
  }

postProcessorPropertyValues()方法的源码如下,在该方法中,会先调用 findAutowiringMetadata()方法解析出 bean 中带有@Autowired 注解、@Inject 和@Value 注解的属性和方法。然后调用 metadata.inject()方法,进行属性填充。

  public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
      //@Autowired注解、@Inject和@Value注解的属性和方法
      InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs);

      try {
          //属性填充
          metadata.inject(bean, beanName, pvs);
          return pvs;
      } catch (BeanCreationException var6) {
          throw var6;
      } catch (Throwable var7) {
          throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var7);
      }
  }

17.说说什么是 AOP?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过分离横切关注点(cross-cutting concerns)来提高代码的模块化。横切关注点是指那些在多个模块中都会涉及的功能,例如日志记录、安全检查、事务管理等。AOP 通过将这些横切关注点从业务逻辑中分离出来,使代码更加清晰、可维护。

AOP 的核心概念

  1. 切面(Aspect)
    • 切面是横切关注点的模块化实现。它可以包含多个通知(advice)和切点(pointcut)。
    • 例如,一个日志切面可以包含记录方法调用的通知。
  2. 通知(Advice)
    • 通知是切面中的具体动作,它定义了在切点处执行的代码。
    • 通知类型包括前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
  3. 切点(Pointcut)
    • 切点定义了在哪些连接点(Join Point)上执行通知。它通过表达式来匹配连接点。
    • 例如,可以定义一个切点匹配所有以 get 开头的方法。
  4. 连接点(Join Point)
    • 连接点是程序执行中的一个特定点,例如方法调用或异常抛出。
    • 在 Spring AOP 中,连接点主要是方法调用。
  5. 目标对象(Target Object)
    • 目标对象是被一个或多个切面增强的对象。也称为被代理对象(proxied object)。
  6. 代理(Proxy)
    • 代理是 AOP 框架创建的对象,用于实现切面功能。代理对象包含目标对象的所有方法,并在适当的时候调用通知。
    • Spring AOP 使用 JDK 动态代理或 CGLIB 代理来创建代理对象。
  7. 织入(Weaving)
    • 织入是将切面应用到目标对象并创建代理对象的过程。织入可以在编译时、类加载时或运行时进行。
    • Spring AOP 主要在运行时进行织入。

织入有哪几种方式?

①、编译期织入:切面在目标类编译时被织入。

②、类加载期织入:切面在目标类加载到 JVM 时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。

③、运行期织入:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。

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

AspectJ是什么?

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

AOP有几种环绕方式?

AOP 一般有 5 种环绕方式:

前置环绕(Before):

在目标方法执行之前执行自定义逻辑。

后置环绕(After):

在目标方法执行之后执行自定义逻辑。

返回环绕(AfterReturning):

在目标方法成功返回之后执行自定义逻辑,可以修改返回值。

异常环绕(AfterThrowing):

在目标方法抛出异常之后执行自定义逻辑,可以处理或修改异常。

完整环绕(Around):

在目标方法执行之前和之后都执行自定义逻辑,可以完全控制目标方法的执行。

Spring AOP 示例

以下是一个简单的 Spring AOP 示例,展示了如何使用 AOP 来记录方法调用的日志。

定义切面

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBeforeMethod(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfterMethod(JoinPoint joinPoint) {
        System.out.println("After method: " + joinPoint.getSignature().getName());
    }
}

配置 AOP

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    // 配置类,启用 AOP 支持
}

目标对象

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

使用目标对象

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

总结- 17

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

AOP 的核心概念包括切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)和织入(Weaving)等。

① 像日志打印、事务管理等都可以抽离为切面,可以声明在类的方法上。像 @Transactional 注解,就是一个典型的 AOP 应用,它就是通过 AOP 来实现事务管理的。我们只需要在方法上添加 @Transactional 注解,Spring 就会在方法执行前后添加事务管理的逻辑。

② Spring AOP 是基于代理的,它默认使用 JDK 动态代理和 CGLIB 代理来实现 AOP。

③ Spring AOP 的织入方式是运行时织入,而 AspectJ 支持编译时织入、类加载时织入。

18.说说 JDK 动态代理和 CGLIB 代理?

在 Spring AOP 中,代理是实现切面功能的核心机制。Spring AOP 主要使用两种代理方式:JDK 动态代理和 CGLIB 代理。以下是这两种代理方式的详细介绍:

JDK 动态代理

描述:JDK 动态代理是基于 Java 反射机制的一种代理方式,它只能代理实现了接口的类。

特点

  • 接口代理:JDK 动态代理只能代理实现了接口的类。
  • 运行时生成:代理类在运行时动态生成,而不是在编译时生成。
  • 性能:由于使用了反射机制,性能相对较低,但在大多数情况下是可以接受的。

示例

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

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

        // 创建代理对象
        MyService proxy = (MyService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new MyInvocationHandler(target)
        );

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

interface MyService {
    void performTask();
}

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

class MyInvocationHandler implements InvocationHandler {
    private final Object target;

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

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

CGLIB 代理

描述:CGLIB(Code Generation Library)代理是基于字节码生成的一种代理方式,它可以代理没有实现接口的类。

特点

  • 子类代理:CGLIB 代理通过生成目标类的子类来实现代理,因此可以代理没有实现接口的类。
  • 运行时生成:代理类在运行时动态生成,而不是在编译时生成。
  • 性能:由于直接操作字节码,性能比 JDK 动态代理高。

示例

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

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

        // 创建代理对象
        MyService proxy = (MyService) Enhancer.create(
                target.getClass(),
                new MyMethodInterceptor(target)
        );

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

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

class MyMethodInterceptor implements MethodInterceptor {
    private final Object target;

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

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = proxy.invoke(target, args);
        System.out.println("After method: " + method.getName());
        return result;
    }
}

Spring AOP 中的代理选择

Spring AOP 默认会根据目标类是否实现了接口来选择代理方式:

  • JDK 动态代理:如果目标类实现了接口,Spring AOP 会使用 JDK 动态代理。
  • CGLIB 代理:如果目标类没有实现接口,Spring AOP 会使用 CGLIB 代理。

你也可以通过配置强制使用 CGLIB 代理:

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
    // 配置类,启用 AOP 支持,并强制使用 CGLIB 代理
}

总结- 18

在 Spring AOP 中,代理是实现切面功能的核心机制。Spring AOP 主要使用两种代理方式:

  1. JDK 动态代理
    • 基于 Java 反射机制。
    • 只能代理实现了接口的类。
    • 代理类在运行时动态生成。
    • 性能相对较低,但在大多数情况下是可以接受的。
  2. CGLIB 代理
    • 基于字节码生成。
    • 可以代理没有实现接口的类。
    • 代理类在运行时动态生成。
    • 性能比 JDK 动态代理高。

通过理解这两种代理方式的特点和使用场景,可以更好地选择和使用 Spring AOP 提供的代理机制,提高代码的模块化和可维护性。

19.说说 Spring AOP 和 AspectJ AOP 区别?

Spring AOP

Spring AOP 属于运行时增强,主要具有如下特点:

基于动态代理来实现,默认如果使用接口的,用 JDK 提供的动态代理实现,如果是方法则使用 CGLIB 实现

Spring AOP 需要依赖 IoC 容器来管理,并且只能作用于 Spring 容器,使用纯 Java 代码实现

在性能上,由于 Spring AOP 是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 的那么好。

Spring AOP 致力于解决企业级开发中最普遍的 AOP(方法织入)。

AspectJ

AspectJ 是一个易用的功能强大的 AOP 框架,属于编译时增强, 可以单独使用,也可以整合到其它框架中,是 AOP 编程的完全解决方案。AspectJ 需要用到单独的编译器 ajc。

AspectJ 属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的,一般有如下几个织入的时机:

编译期织入(Compile-time weaving):如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。

编译后织入(Post-compile weaving):也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。

类加载后织入(Load-time weaving):指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法

20.Spring 事务的种类?

在 Spring 中,事务管理可以分为两大类:声明式事务管理和编程式事务管理。

编程式事务可以使用 TransactionTemplate 和 PlatformTransactionManager 来实现,需要显式执行事务。允许我们在代码中直接控制事务的边界,通过编程方式明确指定事务的开始、提交和回滚。

public class AccountService {
    private TransactionTemplate transactionTemplate;

    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    public void transfer(final String out, final String in, final Double money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                // 转出
                accountDao.outMoney(out, money);
                // 转入
                accountDao.inMoney(in, money);
            }
        });
    }
}

在上面的代码中,我们使用了 TransactionTemplate 来实现编程式事务,通过 execute 方法来执行事务,这样就可以在方法内部实现事务的控制。

声明式事务是建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在目标方法执行完之后根据执行情况提交或者回滚事务。

相比较编程式事务,优点是不需要在业务逻辑代码中掺杂事务管理的代码, Spring 推荐通过 @Transactional 注解的方式来实现声明式事务管理,也是日常开发中最常用的。

不足的地方是,声明式事务管理最细粒度只能作用到方法级别,无法像编程式事务那样可以作用到代码块级别。

@Service
public class AccountService {
    @Autowired
    private AccountDao accountDao;

    @Transactional
    public void transfer(String out, String in, Double money) {
        // 转出
        accountDao.outMoney(out, money);
        // 转入
        accountDao.inMoney(in, money);
    }
}
  • 编程式事务管理:需要在代码中显式调用事务管理的 API 来控制事务的边界,比较灵活,但是代码侵入性较强,不够优雅。
  • 声明式事务管理:这种方式使用 Spring 的 AOP 来声明事务,将事务管理代码从业务代码中分离出来。优点是代码简洁,易于维护。但缺点是不够灵活,只能在预定义的方法上使用事务。

21.Spring的事务隔离级别?

在 Spring 中,事务隔离级别(Transaction Isolation Level)定义了一个事务与其他事务之间的隔离程度。不同的隔离级别可以防止不同类型的并发问题,如脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)。Spring 事务管理支持以下几种隔离级别:

1. ISOLATION_DEFAULT

描述:这是 Spring 的默认隔离级别,使用底层数据库的默认隔离级别。对于大多数数据库来说,默认隔离级别通常是 READ_COMMITTED。MySQL 默认的是可重复读,Oracle 默认的读已提交。

2. ISOLATION_READ_UNCOMMITTED

描述:允许一个事务读取另一个事务未提交的数据。这种隔离级别可能会导致脏读、不可重复读和幻读。

并发问题

  • 脏读(Dirty Read):一个事务读取了另一个事务未提交的数据。
  • 不可重复读(Non-repeatable Read):一个事务在两次读取之间,另一个事务修改了数据。
  • 幻读(Phantom Read):一个事务在两次读取之间,另一个事务插入了新的数据。

3. ISOLATION_READ_COMMITTED

描述:一个事务只能读取另一个事务已提交的数据。这种隔离级别可以防止脏读,但仍然可能会出现不可重复读和幻读。

并发问题

  • 不可重复读(Non-repeatable Read):一个事务在两次读取之间,另一个事务修改了数据。
  • 幻读(Phantom Read):一个事务在两次读取之间,另一个事务插入了新的数据。

4. ISOLATION_REPEATABLE_READ

描述:确保在同一个事务中多次读取数据时,数据是一致的。这种隔离级别可以防止脏读和不可重复读,但仍然可能会出现幻读。

并发问题

  • 幻读(Phantom Read):一个事务在两次读取之间,另一个事务插入了新的数据。

5. ISOLATION_SERIALIZABLE

描述:这是最高的隔离级别,确保事务完全隔离。这种隔离级别可以防止脏读、不可重复读和幻读,但代价是性能较低,因为它通常通过锁定表或行来实现, 但性能影响也最大。

并发问题

  • 无并发问题。

示例

以下是如何在 Spring 中设置事务隔离级别的示例:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountService {

    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void transfer(String out, String in, Double money) {
        // 转出
        accountDao.outMoney(out, money);
        // 转入
        accountDao.inMoney(in, money);
    }
}

在上面的示例中,@Transactional 注解的 isolation 属性用于设置事务的隔离级别。在这个例子中,事务的隔离级别被设置为 READ_COMMITTED

总结- 21

Spring 支持以下几种事务隔离级别:

  1. ISOLATION_DEFAULT:使用底层数据库的默认隔离级别。
  2. ISOLATION_READ_UNCOMMITTED:允许读取未提交的数据,可能会导致脏读、不可重复读和幻读。
  3. ISOLATION_READ_COMMITTED:只能读取已提交的数据,防止脏读,但可能会出现不可重复读和幻读。
  4. ISOLATION_REPEATABLE_READ:确保多次读取数据时数据一致,防止脏读和不可重复读,但可能会出现幻读。
  5. ISOLATION_SERIALIZABLE:最高的隔离级别,确保事务完全隔离,防止所有并发问题,但性能较低。

通过理解和选择合适的事务隔离级别,可以在保证数据一致性的同时,提高应用程序的性能。

22.Spring 的事务传播机制?

Spring 的事务传播机制定义了事务在不同方法调用之间的传播方式。通过设置事务传播属性,可以控制事务在方法调用过程中如何传播和管理。Spring 提供了多种事务传播行为,主要包括以下几种:

1. PROPAGATION_REQUIRED

描述:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

应用场景:这是最常用的传播行为,适用于大多数业务场景。

2. PROPAGATION_REQUIRES_NEW

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

应用场景:适用于需要在一个新的事务中执行操作,并且不受当前事务影响的场景。

3. PROPAGATION_SUPPORTS

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

应用场景:适用于既可以在事务中执行,也可以在非事务中执行的场景。

4. PROPAGATION_NOT_SUPPORTED

描述:以非事务方式执行操作,如果当前存在事务,则挂起当前事务。

应用场景:适用于不需要事务支持的操作,并且需要确保当前没有事务的场景。

5. PROPAGATION_NEVER

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

应用场景:适用于必须确保当前没有事务的场景。

6. PROPAGATION_MANDATORY

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

应用场景:适用于必须在现有事务中执行的操作。

7. PROPAGATION_NESTED

描述:如果当前存在事务,则创建一个事务嵌套(Savepoint);如果当前没有事务,则创建一个新的事务。

应用场景:适用于需要在一个事务中执行多个子事务,并且子事务可以独立回滚的场景。

事务传播的机制实现

事务传播机制是使用 ThreadLocal 实现的,所以,如果调用的方法是在新线程中,事务传播会失效。

@Transactional
public void parentMethod() {
    new Thread(() -> childMethod()).start();
}

public void childMethod() {
    // 这里的操作将不会在 parentMethod 的事务范围内执行
}

Spring 默认的事务传播行为是 PROPAFATION_REQUIRED,即如果多个 ServiceX#methodX() 都工作在事务环境下,且程序中存在这样的调用链 Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法都通过 Spring 的事务传播机制工作在同一个事务中。

总结- 22

Spring 提供了多种事务传播行为,用于控制事务在不同方法调用之间的传播方式:

  1. PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则挂起当前事务。
  3. PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  4. PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起当前事务。
  5. PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  6. PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  7. PROPAGATION_NESTED:如果当前存在事务,则创建一个事务嵌套;如果当前没有事务,则创建一个新的事务。

通过理解和选择合适的事务传播行为,可以更灵活地控制事务的传播和管理,确保数据的一致性和完整性。

23.protected 和 private 加事务会生效吗

在 Spring 中,只有通过 Spring 容器的 AOP 代理调用的公开方法(public method)上的@Transactional注解才会生效。

如果在 protected、private 方法上使用@Transactional,这些事务注解将不会生效,原因:Spring 默认使用基于 JDK 的动态代理(当接口存在时)或基于 CGLIB 的代理(当只有类时)来实现事务。这两种代理机制都只能代理公开的方法。

23.声明式事务实现原理了解吗?

Spring 的声明式事务管理是通过 AOP(面向切面编程)和代理机制实现的。

第一步,在 Bean 初始化阶段创建代理对象

Spring 容器在初始化单例 Bean 的时候,会遍历所有的 BeanPostProcessor 实现类,并执行其 postProcessAfterInitialization 方法。

在执行 postProcessAfterInitialization 方法时会遍历容器中所有的切面,查找与当前 Bean 匹配的切面,这里会获取事务的属性切面,也就是 @Transactional 注解及其属性值。

然后根据得到的切面创建一个代理对象,默认使用 JDK 动态代理创建代理,如果目标类是接口,则使用 JDK 动态代理,否则使用 Cglib。

第二步,在执行目标方法时进行事务增强操作

当通过代理对象调用 Bean 方法的时候,会触发对应的 AOP 增强拦截器,声明式事务是一种环绕增强,对应接口为MethodInterceptor,事务增强对该接口的实现为TransactionInterceptor

事务拦截器TransactionInterceptor在invoke方法中,通过调用父类TransactionAspectSupport的invokeWithinTransaction方法进行事务处理,包括开启事务、事务提交、异常回滚等。

24.声明式事务在哪些情况下会失效?

@Transactional 应用在非 public 修饰的方法上

如果 Transactional 注解应用在非 public 修饰的方法上,Transactional 将会失效。

是因为在 Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource 的 computeTransactionAttribute方法,获取 Transactional 注解的事务配置信息。

protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
    }
}

此方法会检查目标方法的修饰符是否为 public,不是 public 则不会获取 @Transactional 的属性配置信息。

@Transactional 注解属性 propagation 设置错误

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行;错误使用场景:在业务逻辑必须运行在事务环境下以确保数据一致性的情况下使用 SUPPORTS。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:总是以非事务方式执行,如果当前存在事务,则挂起该事务。错误使用场景:在需要事务支持的操作中使用 NOT_SUPPORTED。
  • TransactionDefinition.PROPAGATION_NEVER:总是以非事务方式执行,如果当前存在事务,则抛出异常。错误使用场景:在应该在事务环境下执行的操作中使用 NEVER。

@Transactional 注解属性 rollbackFor 设置错误

rollbackFor 用来指定能够触发事务回滚的异常类型。Spring 默认抛出未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)

若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

同一个类中方法调用,导致@Transactional 失效

开发中避免不了会对同一个类里面的方法调用,比如有一个类 Test,它的一个方法 A,A 调用本类的方法 B(不论方法 B 是用 public 还是 private 修饰),但方法 A 没有声明注解事务,而 B 方法有。

则外部调用方法 A 之后,方法 B 的事务是不会起作用的。这也是经常犯错误的一个地方。

那为啥会出现这种情况呢?其实还是由 Spring AOP 代理造成的,因为只有事务方法被当前类以外的代码调用时,才会由 Spring 生成的代理对象来管理。

 //@Transactional
@GetMapping("/test")
private Integer A() throws Exception {
    CityInfoDict cityInfoDict = new CityInfoDict();
    cityInfoDict.setCityName("2");
    /**
     * B 插入字段为 3的数据
     */
    this.insertB();
    /**
     * A 插入字段为 2的数据
     */
    int insert = cityInfoDictMapper.insert(cityInfoDict);
    return insert;
}

@Transactional()
public Integer insertB() throws Exception {
    CityInfoDict cityInfoDict = new CityInfoDict();
    cityInfoDict.setCityName("3");
    cityInfoDict.setParentCityId(3);

    return cityInfoDictMapper.insert(cityInfoDict);
}

这种情况是最常见的一种@Transactional 注解失效场景。

@Transactional
private Integer A() throws Exception {
    int insert = 0;
    try {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        cityInfoDict.setParentCityId(2);
        /**
         * A 插入字段为 2的数据
         */
        insert = cityInfoDictMapper.insert(cityInfoDict);
        /**
         * B 插入字段为 3的数据
        */
        b.insertB();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

如果 B 方法内部抛了异常,而 A 方法此时 try catch 了 B 方法的异常,那这个事务就不能正常回滚了,会抛出异常:

25.Spring MVC 的核心组件?

  • DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥。
  • Handler:处理器,完成具体的业务逻辑,相当于 Servlet 或 Action。
  • HandlerMapping:DispatcherServlet 接收到请求之后,通过 - - HandlerMapping 将不同的请求映射到不同的 Handler。
  • HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。
  • HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)。
  • HandlerAdapter:处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由 HandlerApater 来完成,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler。
  • ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
  • ViewResolver:视图解析器,DispatcheServlet 通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端。

26.Spring MVC 的工作流程?

Spring MVC 是基于模型-视图-控制器的 Web 框架,它的工作流程也主要是围绕着 Model、View、Controller 这三个组件展开的。

①、发起请求:客户端通过 HTTP 协议向服务器发起请求。

②、前端控制器:这个请求会先到前端控制器 DispatcherServlet,它是整个流程的入口点,负责接收请求并将其分发给相应的处理器。

③、处理器映射:DispatcherServlet 调用 HandlerMapping 来确定哪个 Controller 应该处理这个请求。通常会根据请求的 URL 来确定。

④、处理器适配器:一旦找到目标 Controller,DispatcherServlet 会使用 HandlerAdapter 来调用 Controller 的处理方法。

⑤、执行处理器:Controller 处理请求,处理完后返回一个 ModelAndView 对象,其中包含模型数据和逻辑视图名。

⑥、视图解析器:DispatcherServlet 接收到 ModelAndView 后,会使用 ViewResolver 来解析视图名称,找到具体的视图页面。

⑦、渲染视图:视图使用模型数据渲染页面,生成最终的页面内容。

⑧、响应结果:DispatcherServlet 将视图结果返回给客户端。

Spring MVC 虽然整体流程复杂,但是实际开发中很简单,大部分的组件不需要我们开发人员创建和管理,真正需要处理的只有 Controller 、View 、Model。

在前后端分离的情况下,步骤 ⑥、⑦、⑧ 会略有不同,后端通常只需要处理数据,并将 JSON 格式的数据返回给前端就可以了,而不是返回完整的视图页面。

Handler 一般就是指 Controller,Controller 是 Spring MVC 的核心组件,负责处理请求,返回响应。

Spring MVC 允许使用多种类型的处理器。不仅仅是标准的@Controller注解的类,还可以是实现了特定接口的其他类(如 HttpRequestHandler 或 SimpleControllerHandlerAdapter 等)。这些处理器可能有不同的方法签名和交互方式。

HandlerAdapter 的主要职责就是调用 Handler 的方法来处理请求,并且适配不同类型的处理器。HandlerAdapter 确保 DispatcherServlet 可以以统一的方式调用不同类型的处理器,无需关心具体的执行细节。

27.SpringMVC Restful 风格的接口的流程是什么样的呢?

我们都知道 Restful 接口,响应格式是 json,这就用到了一个常用注解:@ResponseBody

    @GetMapping("/user")
    @ResponseBody
    public User user(){
        return new User(1,"张三");
    }

加入了这个注解后,整体的流程上和使用 ModelAndView 大体上相同,但是细节上有一些不同:

客户端向服务端发送一次请求,这个请求会先到前端控制器 DispatcherServlet

DispatcherServlet 接收到请求后会调用 HandlerMapping 处理器映射器。由此得知,该请求该由哪个 Controller 来处理

DispatcherServlet 调用 HandlerAdapter 处理器适配器,告诉处理器适配器应该要去执行哪个 Controller

Controller 被封装成了 ServletInvocableHandlerMethod,HandlerAdapter 处理器适配器去执行 invokeAndHandle 方法,完成对 Controller 的请求处理

HandlerAdapter 执行完对 Controller 的请求,会调用 HandlerMethodReturnValueHandler 去处理返回值,主要的过程:

  • 调用 RequestResponseBodyMethodProcessor,创建 ServletServerHttpResponse(Spring 对原生 ServerHttpResponse 的封装)实例

  • 使用 HttpMessageConverter 的 write 方法,将返回值写入 ServletServerHttpResponse 的 OutputStream 输出流中

  • 在写入的过程中,会使用 JsonGenerator(默认使用 Jackson 框架)对返回值进行 Json 序列化

执行完请求后,返回的 ModealAndView 为 null,ServletServerHttpResponse 里也已经写入了响应,所以不用关心 View 的处理

28.介绍一下 SpringBoot,有哪些优点?

Spring Boot 是由 Pivotal 团队提供的一个框架,用于简化 Spring 应用的创建、配置和部署。它基于 Spring 框架,提供了一种快速构建生产级 Spring 应用的方式。Spring Boot 通过约定优于配置(Convention over Configuration)的理念,减少了开发人员的工作量,使得开发过程更加高效。

Spring Boot 的优点

  1. 快速入门
    • Spring Boot 提供了大量的开箱即用的功能,通过简化配置和自动化配置,使得开发人员可以快速启动一个新的 Spring 项目。
    • 提供了 Spring Initializr 工具,可以通过 Web 界面或命令行快速生成项目骨架。
  2. 自动配置
    • Spring Boot 提供了自动配置功能,可以根据项目中的依赖和配置自动配置 Spring 应用,无需手动编写大量的配置文件。
    • 自动配置可以通过注解 @EnableAutoConfiguration@SpringBootApplication 启用。
  3. 内嵌服务器
    • Spring Boot 支持内嵌的 Web 服务器,如 Tomcat、Jetty 和 Undertow,使得应用可以打包成一个可执行的 JAR 文件,直接运行,无需外部服务器。
    • 这简化了部署过程,特别适合微服务架构。
  4. 简化的依赖管理
    • Spring Boot 提供了依赖管理的起步依赖(Starters),这些起步依赖是预先定义的一组依赖,可以简化 Maven 或 Gradle 配置。
    • 例如,spring-boot-starter-web 包含了构建 Web 应用所需的所有依赖。
  5. 生产级特性
    • Spring Boot 提供了许多生产级特性,如监控、度量、健康检查和外部化配置。
    • 通过 spring-boot-starter-actuator 起步依赖,可以轻松添加这些特性。
  6. 外部化配置
    • Spring Boot 支持通过外部配置文件(如 application.propertiesapplication.yml)来配置应用。
    • 支持多种配置源,如环境变量、命令行参数、配置服务器等。
  7. 开发者友好
    • Spring Boot 提供了开发者工具(DevTools),可以实现热部署、自动重启和调试功能,提高开发效率。
    • 提供了丰富的日志和错误信息,帮助开发者快速定位和解决问题。
  8. 社区支持
    • Spring Boot 拥有庞大的社区支持和丰富的文档资源,开发者可以轻松找到所需的帮助和示例。
    • 定期发布新版本,持续改进和更新。

示例代码

以下是一个简单的 Spring Boot 应用示例,展示了如何快速创建一个 RESTful Web 服务:

1. 创建 Spring Boot 项目

可以通过 Spring Initializr 工具生成项目骨架,选择需要的依赖,如 Spring Web。

2. 主应用类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MySpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class, args);
    }
}

3. 控制器类

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, Spring Boot!";
    }
}

4. 配置文件(application.properties)

server.port=8080

总结- 29

Spring Boot 是一个强大的框架,提供了快速构建、配置和部署 Spring 应用的能力。其主要优点包括:

  1. 快速入门:简化配置和自动化配置,快速启动新项目。
  2. 自动配置:根据依赖和配置自动配置应用。
  3. 内嵌服务器:支持内嵌 Web 服务器,简化部署。
  4. 简化的依赖管理:提供起步依赖,简化依赖管理。
  5. 生产级特性:提供监控、度量、健康检查等生产级特性。
  6. 外部化配置:支持通过外部配置文件配置应用。
  7. 开发者友好:提供开发者工具,提高开发效率。
  8. 社区支持:拥有庞大的社区支持和丰富的文档资源。

通过理解和利用这些优点,开发者可以更高效地构建和部署 Spring 应用。

30.SpringBoot 自动配置原理了解吗?

在 Spring 中,自动装配是指容器利用反射技术,根据 Bean 的类型、名称等自动注入所需的依赖。

在 Spring Boot 中,开启自动装配的注解是@EnableAutoConfiguration。

Spring Boot 为了进一步简化,直接通过 @SpringBootApplication 注解一步搞定,这个注解包含了 @EnableAutoConfiguration 注解。

①、@EnableAutoConfiguration 只是一个简单的注解,但是它的背后却是一个非常复杂的自动装配机制,核心是AutoConfigurationImportSelector 类。

@AutoConfigurationPackage //将main同级的包下的所有组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) //加载自动装配类 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

②、AutoConfigurationImportSelector实现了ImportSelector接口,这个接口的作用就是收集需要导入的配置类,配合@Import()就将相应的类导入到 Spring 容器中。

③、获取注入类的方法是 selectImports(),它实际调用的是getAutoConfigurationEntry(),这个方法是获取自动装配类的关键。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 检查自动配置是否启用。如果@ConditionalOnClass等条件注解使得自动配置不适用于当前环境,则返回一个空的配置条目。
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }

    // 获取启动类上的@EnableAutoConfiguration注解的属性,这可能包括对特定自动配置类的排除。
    AnnotationAttributes attributes = getAttributes(annotationMetadata);

    // 从spring.factories中获取所有候选的自动配置类。这是通过加载META-INF/spring.factories文件中对应的条目来实现的。
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

    // 移除配置列表中的重复项,确保每个自动配置类只被考虑一次。
    configurations = removeDuplicates(configurations);

    // 根据注解属性解析出需要排除的自动配置类。
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);

    // 检查排除的类是否存在于候选配置中,如果存在,则抛出异常。
    checkExcludedClasses(configurations, exclusions);

    // 从候选配置中移除排除的类。
    configurations.removeAll(exclusions);

    // 应用过滤器进一步筛选自动配置类。过滤器可能基于条件注解如@ConditionalOnBean等来排除特定的配置类。
    configurations = getConfigurationClassFilter().filter(configurations);

    // 触发自动配置导入事件,允许监听器对自动配置过程进行干预。
    fireAutoConfigurationImportEvents(configurations, exclusions);

    // 创建并返回一个包含最终确定的自动配置类和排除的配置类的AutoConfigurationEntry对象。
    return new AutoConfigurationEntry(configurations, exclusions);
}

Spring Boot 的自动装配原理依赖于 Spring 框架的依赖注入和条件注册,通过这种方式,Spring Boot 能够智能地配置 bean,并且只有当这些 bean 实际需要时才会被创建和配置。

31.如何自定义一个 SpringBoot Srarter?

创建一个自定义的 Spring Boot Starter,需要这几步:

第一步,创建一个新的 Maven 项目,例如命名为 my-spring-boot-starter。在 pom.xml 文件中添加必要的依赖和配置:

<properties>
    <spring.boot.version>2.3.1.RELEASE</spring.boot.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
        <version>${spring.boot.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>${spring.boot.version}</version>
    </dependency>
</dependencies>

第二步,在 src/main/java 下创建一个自动配置类,比如 MyServiceAutoConfiguration.java:(通常是 autoconfigure 包下)。

@Configuration
@EnableConfigurationProperties(MyStarterProperties.class)
public class MyServiceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService myService(MyStarterProperties properties) {
        return new MyService(properties.getMessage());
    }
}

第三步,创建一个配置属性类 MyStarterProperties.java:

@ConfigurationProperties(prefix = "mystarter")
public class MyStarterProperties {
    private String message = "Hello Java !";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

第四步,创建一个简单的服务类 MyService.java:

public class MyService {
    private final String message;

    public MyService(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

第五步,配置 spring.factories,在 src/main/resources/META-INF 目录下创建 spring.factories 文件,并添加:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itwanger.mystarter.autoconfigure.MyServiceAutoConfiguration

第六步,使用 Maven 打包这个项目:

mvn clean install

第七步,在其他的 Spring Boot 项目中,通过 Maven 来添加这个自定义的 Starter 依赖,并通过 application.properties 配置欢迎消息:

mystarter.message=javabetter.cn

然后就可以在 Spring Boot 项目中注入 MyStarterProperties 来使用它

32.Spring Boot Starter 的原理了解吗?

Spring Boot Starter 主要通过起步依赖和自动配置机制来简化项目的构建和配置过程。

起步依赖是 Spring Boot 提供的一组预定义依赖项,它们将一组相关的库和模块打包在一起。比如 spring-boot-starter-web 就包含了 Spring MVC、Tomcat 和 Jackson 等依赖。

自动配置机制是 Spring Boot 的核心特性,通过自动扫描类路径下的类、资源文件和配置文件,自动创建和配置应用程序所需的 Bean 和组件。

比如有了 spring-boot-starter-web,我们开发者就不需要再手动配置 Tomcat、Spring MVC 等,Spring Boot 会自动帮我们完成这些工作。

33.Spring Boot 启动原理了解吗?

Spring Boot 应用通常有一个带有 main 方法的主类,这个类上标注了 @SpringBootApplication 注解,它是整个应用启动的入口。这个注解组合了 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan,这些注解共同支持配置和类路径扫描。

当执行 main 方法时,首先创建一个 SpringApplication 的实例。这个实例负责管理 Spring 应用的启动和初始化。

SpringApplication.run() 方法负责准备和启动 Spring 应用上下文(ApplicationContext)环境,包括:

  • 扫描配置文件,添加依赖项
  • 初始化和加载 Bean 定义
  • 启动内嵌的 Web 服务器

了解@SpringBootApplication 注解吗?

@SpringBootApplication是 Spring Boot 的核心注解,经常用于主类上,作为项目启动入口的标识。它是一个组合注解:

  • @SpringBootConfiguration:继承自 @Configuration,标注该类是一个配置类,相当于一个 Spring 配置文件。
  • @EnableAutoConfiguration:告诉 Spring Boot 根据 pom.xml 中添加的依赖自动配置项目。例如,如果 spring-boot-starter-web 依赖被添加到项目中,Spring Boot 会自动配置 Tomcat 和 Spring MVC。
  • @ComponentScan:扫描当前包及其子包下被@Component、@Service、@Controller、@Repository 注解标记的类,并注册为 Spring Bean。
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

为什么 Spring Boot 在启动的时候能够找到 main 方法上的@SpringBootApplication 注解?

Spring Boot 在启动时能够找到主类上的@SpringBootApplication注解,是因为它利用了 Java 的反射机制和类加载机制,结合 Spring 框架内部的一系列处理流程。

当运行一个 Spring Boot 程序时,通常会调用主类中的main方法,这个方法会执行SpringApplication.run(),比如:

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

SpringApplication.run(Class<?> primarySource, String… args)方法接收两个参数:第一个是主应用类(即包含main方法的类),第二个是命令行参数。primarySource参数提供了一个起点,Spring Boot 通过它来加载应用上下文。

Spring Boot 利用 Java 反射机制来读取传递给run方法的类(MyApplication.class)。它会检查这个类上的注解,包括@SpringBootApplication。

Spring Boot 默认的包扫描路径是什么?

Spring Boot 的默认包扫描路径是以启动类 @SpringBootApplication 注解所在的包为根目录的,即默认情况下,Spring Boot 会扫描启动类所在包及其子包下的所有组件。

@SpringBootApplication 是一个组合注解,它里面的@ComponentScan注解可以指定要扫描的包路径,默认扫描启动类所在包及其子包下的所有组件。

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

比如说带有 @Component、@Service、@Controller、@Repository 等注解的类都会被 Spring Boot 扫描到,并注册到 Spring 容器中。

如果需要自定义包扫描路径,可以在@SpringBootApplication注解上添加@ComponentScan注解,指定要扫描的包路径。

@SpringBootApplication
@ComponentScan(basePackages = {"com.github.paicoding.forum"})
public class QuickForumApplication {
    public static void main(String[] args) {
        SpringApplication.run(QuickForumApplication.class, args);
    }
}

这种方式会覆盖默认的包扫描路径,只扫描com.github.paicoding.forum包及其子包下的所有组件。

34.SpringBoot 和 SpringMVC 的区别?

Spring MVC 是基于 Spring 框架的一个模块,提供了一种 Model-View-Controller(模型-视图-控制器)的开发模式。

Spring Boot 旨在简化 Spring 应用的配置和部署过程,提供了大量的自动配置选项,以及运行时环境的内嵌 Web 服务器,这样就可以更快速地开发一个 SpringMVC 的 Web 项目。

35.Spring Boot 和 Spring 有什么区别?

Spring Boot 是 Spring Framework 的一个扩展,提供了一套快速配置和开发的框架,可以帮助我们快速搭建 Spring 项目骨架,极大地提高了我们的生产效率。

特性 Spring Framework Spring Boot
目的 提供全面的企业级开发工具和库 简化 Spring 应用的开发、配置和部署
配置方式 主要通过 XML 和注解配置 主要通过注解和外部配置文件
启动和运行 需要手动配置和部署到服务器 支持嵌入式服务器,打包成 JAR 文件直接运行
自动配置 手动配置各种组件和依赖 提供开箱即用的自动配置
依赖管理 手动添加和管理依赖 使用 spring-boot-starter 简化依赖管理
模块化 高度模块化,可以选择使用不同的模块 集成多个常用模块,提供统一的启动入口
生产准备功能 需要手动集成和配置 内置监控、健康检查等生产准备功能

36.对 SpringCloud 了解多少?

Spring Cloud 是一个基于 Spring Boot,提供构建分布式系统和微服务架构的工具集。用于解决分布式系统中的一些常见问题,如配置管理、服务发现、负载均衡等等。

Spring Cloud 的核心组件

  1. Spring Cloud Config
    • 提供集中化的配置管理,支持从 Git、SVN 等版本控制系统中读取配置。
    • 支持动态刷新配置,方便在运行时更新配置而无需重启服务。
  2. Spring Cloud Netflix
    • 集成了 Netflix 的开源项目,如 Eureka、Ribbon、Hystrix、Zuul 等。
    • Eureka:服务注册与发现。
    • Ribbon:客户端负载均衡。
    • Hystrix:断路器,提供容错机制。
    • Zuul:API 网关,提供路由和过滤功能。
  3. Spring Cloud Gateway
    • 作为 Zuul 的替代品,提供更强大的路由和过滤功能。
    • 支持动态路由、断路器、限流等功能。
  4. Spring Cloud OpenFeign
    • 声明式的 HTTP 客户端,简化了服务之间的通信。
    • 集成了 Ribbon 和 Hystrix,提供负载均衡和容错机制。
  5. Spring Cloud Sleuth
    • 提供分布式追踪功能,帮助开发者跟踪请求在微服务中的流转路径。
    • 集成了 Zipkin 和 ELK(Elasticsearch、Logstash、Kibana)等工具。
  6. Spring Cloud Stream
    • 提供消息驱动的微服务框架,支持与 Kafka、RabbitMQ 等消息中间件集成。
    • 简化了消息的生产和消费,提供了统一的编程模型。
  7. Spring Cloud Bus
    • 用于传播集群中的配置变更事件,通常与 Spring Cloud Config 一起使用。
    • 支持广播和点对点消息传递。
  8. Spring Cloud Security
    • 提供安全管理功能,支持 OAuth2 和 JWT 等认证和授权机制。
    • 集成了 Spring Security,提供统一的安全解决方案。

Spring Cloud 的优点

  1. 简化微服务开发
    • 提供了大量开箱即用的组件,简化了微服务的开发和配置。
    • 与 Spring Boot 无缝集成,利用 Spring Boot 的自动配置和依赖管理功能。
  2. 集中化配置管理
    • 通过 Spring Cloud Config 实现集中化配置管理,支持动态刷新配置。
    • 提高了配置管理的效率和一致性。
  3. 服务注册与发现
    • 通过 Eureka 实现服务注册与发现,简化了服务之间的通信。
    • 支持客户端负载均衡,提高了系统的可用性和扩展性。
  4. 容错机制
    • 通过 Hystrix 实现断路器和降级处理,提高了系统的容错能力。
    • 提供了监控和报警功能,帮助开发者及时发现和处理问题。
  5. API 网关
    • 通过 Zuul 或 Spring Cloud Gateway 实现 API 网关,提供路由、过滤、限流等功能。
    • 提高了系统的安全性和可维护性。
  6. 分布式追踪
    • 通过 Spring Cloud Sleuth 实现分布式追踪,帮助开发者跟踪请求在微服务中的流转路径。
    • 提供了丰富的监控和分析工具,帮助开发者优化系统性能。

以下是一个简单的 Spring Cloud 示例,展示了如何使用 Spring Cloud Netflix Eureka 实现服务注册与发现:

1. 创建 Eureka 服务器

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

2. 创建服务提供者

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@EnableEurekaClient
public class ServiceProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceProviderApplication.class, args);
    }
}

@RestController
class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello from Service Provider!";
    }
}

3. 创建服务消费者

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class ServiceConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceConsumerApplication.class, args);
    }
}

@RestController
class HelloController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/hello")
    public String hello() {
        RestTemplate restTemplate = new RestTemplate();
        String serviceUrl = discoveryClient.getInstances("service-provider")
                                           .get(0)
                                           .getUri()
                                           .toString();
        return restTemplate.getForObject(serviceUrl + "/hello", String.class);
    }
}

总结- 36

Spring Cloud 是一个强大的框架,提供了丰富的工具和服务,用于构建和管理微服务架构。其核心组件包括 Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Gateway、Spring Cloud OpenFeign、Spring Cloud Sleuth、Spring Cloud Stream、Spring Cloud Bus 和 Spring Cloud Security。通过理解和使用这些组件,开发者可以更高效地构建和管理分布式系统,提高系统的可用性、扩展性和可维护性。

37.SpringTask 了解吗?

SpringTask 是 Spring 框架提供的一个轻量级的任务调度框架,它允许我们开发者通过简单的注解来配置和管理定时任务。

①、@Scheduled:最常用的注解,用于标记方法为计划任务的执行点。

@Scheduled(cron = "0 15 5 * * ?")
public void autoRefreshCache() {
    log.info("开始刷新sitemap.xml的url地址,避免出现数据不一致问题!");
    refreshSitemap();
    log.info("刷新完成!");
}

@Scheduled 注解支持多种调度选项,如 fixedRate、fixedDelay 和 cron 表达式。

②、@EnableScheduling:用于开启定时任务的支持。

订单超时,用springtask资源占用太高,有什么其他的方式解决?

第一,使用消息队列,如 RabbitMQ、Kafka、RocketMQ 等,将任务放到消息队列中,然后由消费者异步处理这些任务。

①、在订单创建时,将订单超时检查任务放入消息队列,并设置延迟时间(即订单超时时间)。

@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void createOrder(Order order) {
        // 创建订单逻辑
        // ...
        
        // 发送延迟消息
        rabbitTemplate.convertAndSend("orderExchange", "orderTimeoutQueue", order, message -> {
            message.getMessageProperties().setExpiration("600000"); // 设置延迟时间(10分钟)
            return message;
        });
    }
}

②、使用消费者从队列中消费消息,当消费到超时任务时,执行订单超时处理逻辑。

@Service
public class OrderTimeoutConsumer {

    @RabbitListener(queues = "orderTimeoutQueue")
    public void handleOrderTimeout(Order order) {
        // 处理订单超时逻辑
        // ...
    }
}

第二,使用数据库调度器(如 Quartz)。

①、创建一个 Quartz 任务类,处理订单超时逻辑。

public class OrderTimeoutJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 获取订单信息
        Order order = (Order) context.getJobDetail().getJobDataMap().get("order");

        // 处理订单超时逻辑
        // ...
    }
}

②、在订单创建时,调度一个 Quartz 任务,设置任务的触发时间为订单超时时间。

@Service
public class OrderService {
    @Autowired
    private Scheduler scheduler;

    public void createOrder(Order order) {
        // 创建订单逻辑
        // ...

        // 调度 Quartz 任务
        JobDetail jobDetail = JobBuilder.newJob(OrderTimeoutJob.class)
                .usingJobData("order", order)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .startAt(new Date(System.currentTimeMillis() + 600000)) // 设置触发时间(10分钟后)
                .build();

        try {
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}