• Spring理解,重要概念及图解,2023秋招spring常见八股文


    按照自己的需求,找到自己不会的地方去解决

    1.Spring的核心

    1)Spring的两大核心:IoC和AOP

    Spring框架包含众多模块,如Core、Testing、Data Access、Web Servlet等,其中Core是整个Spring框架的核心模块。Core模块提供了IoC容器、AOP功能、数据绑定、类型转换等一系列的基础功能,而这些功能以及其他模块的功能都是建立在IoC和AOP之上的,所以IoC和AOP是Spring框架的核心。

    IoC(Inversion of Control)是控制反转的意思,这是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高,在一个大型的项目中这十分的不利于代码的维护。IoC则可以解决这种问题,它可以帮我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。

    说到IoC就不得不说DI(Dependency Injection),DI是依赖注入的意思,它是IoC实现的实现方式,就是说IoC是通过DI来实现的。由于IoC这个词汇比较抽象而DI却更直观,所以很多时候我们就用DI来代替它,在很多时候我们简单地将IoC和DI划等号,这是一种习惯。而实现依赖注入的关键是IoC容器,它的本质就是一个工厂。

    AOP(Aspect Oriented Programing)是面向切面编程思想,这种思想是对OOP的补充,它可以在OOP的基础上进一步提高编程的效率。简单来说,它可以统一解决一批组件的共性需求(如权限检查、记录日志、事务管理等)。在AOP思想下,我们可以将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方、什么时机调用。当满足调用条件时,AOP会将该业务代码织入到我们指定的位置,从而统一解决了问题,又不需要修改这一批组件的代码。

    2)IOC容器:BeanFactory和ApplicationContext

    1.两种工厂的创建方式:

    2.两种工厂的关系(区别)

    底层图解:

    总结

    3)DI依赖注入

    依赖注入有三种常见的注入方式:构造函数注入、Setter方法注入和接口注入。下面分别展示这三种注入方式的代码体现。

    1. 构造函数注入:

    public class MyApplication {
        private MessageService messageService;
    ​
        // 通过构造函数注入依赖
        public MyApplication(MessageService messageService) {
            this.messageService = messageService;
        }
    ​
        public void processMessage(String message) {
            // 使用依赖对象
            messageService.sendMessage(message);
        }
    }
    1. Setter方法注入:

    public class MyApplication {
        private MessageService messageService;
    ​
        // 通过Setter方法注入依赖
        public void setMessageService(MessageService messageService) {
            this.messageService = messageService;
        }
    ​
        public void processMessage(String message) {
            // 使用依赖对象
            messageService.sendMessage(message);
        }
    }
    1. 接口注入:

    public interface MessageServiceInjector {
        MyApplication getApplication();
    }
    ​
    public class EmailServiceInjector implements MessageServiceInjector {
        public MyApplication getApplication() {
            // 创建依赖对象
            MessageService messageService = new EmailService();
    ​
            // 创建应用程序并注入依赖
            MyApplication application = new MyApplication(messageService);
    ​
            return application;
        }
    }

    在接口注入中,我们定义了一个MessageServiceInjector接口,它包含一个getApplication方法,用于获取MyApplication对象。然后,我们创建了一个EmailServiceInjector类,实现了MessageServiceInjector接口,并在getApplication方法中创建了依赖对象messageService,并将其注入到MyApplication对象中。

    通过以上三种方式,我们可以将依赖对象注入到目标类中,实现依赖的解耦和灵活替换。具体选择哪种注入方式,取决于具体的需求和设计。

    2.AOP和动态代理关系

    动态代理于AOP基本逻辑关系:

    OOP编程的垂直性造成代码冗余,不方便管理;

    解决代码冗余这类问题的思维是AOP面向切面编程思想;

    实现这一思想,就要用动态代理技术;代理技术直接使用很麻烦,所以不会直接用,而是使用spring Aop技术框架来解决该类问题,aop框架底层就用到动态代理技术。

    通过AOP例子理解AOP

    下面是一个简单的Java AOP代码示例:

    1. 定义一个切面类,实现切面逻辑:

    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 beforeAdvice() {
            System.out.println("Before executing the method");
        }
    }
    1. 创建一个服务类,该类中的方法将被切面所拦截:

    import org.springframework.stereotype.Service;
    ​
    @Service
    public class UserService {
    ​
        public void addUser(String username) {
            System.out.println("Adding user: " + username);
        }
    }
    1. 创建一个配置类,启用AOP并扫描切面和服务类:

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    ​
    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan(basePackages = "com.example")
    public class AppConfig {
    ​
    }
    1. 创建一个主类,加载配置类并获取服务类的实例:

    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    ​
    public class MainApp {
    ​
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
            UserService userService = context.getBean(UserService.class);
    ​
            userService.addUser("John");
    ​
            context.close();
        }
    }

    在上述示例中,切面类 LoggingAspect 使用 @Aspect 注解标记为切面,并使用 @Before 注解定义了一个前置通知,该通知会在执行 com.example.service 包下的任何方法之前执行。服务类 UserService 中的 addUser 方法会被切面所拦截,在方法执行之前会打印一条日志。配置类 AppConfig 使用 @EnableAspectJAutoProxy 注解启用了AOP,并通过 @ComponentScan 注解扫描了切面和服务类。主类 MainApp 加载配置类,并获取服务类的实例,然后调用 addUser 方法。

    运行主类 MainApp,将会输出以下内容:

    Before executing the method
    Adding user: John

    这表明切面逻辑在方法执行之前被调用,成功拦截了服务类的方法。

    3.spring中bean的安全问题

    补充:成员方法才存在线程安全问题

    • spring中的bean如果是无状态的(一般情况是),则是线程安全的;

    • spring中的bean如果是有状态的,则需要标注@Scope("prototype"),意思是允许多个实例

      相反的@Scope("singleton"),意思是IOC容器中只允许创建一个实例

    4.事务(来保证操作的原子性)

    补充:事务是数据库中的概念,但spring对事务进行了拓展

    • 在Spring框架中,一般需要在以下情况下使用事务

    1. 数据库操作:当需要进行数据库的增删改操作时,可以使用事务来确保操作的一致性和完整性。例如,插入用户信息和同时插入用户订单信息,这两个操作应该放在同一个事务中,要么都成功,要么都失败。

    2. 多个服务方法调用:当一个服务方法中调用了多个其他服务方法,并且这些方法需要保证一致性,可以使用事务来统一管理。例如,用户下单操作需要调用库存服务和支付服务,这些服务操作应该在同一个事务中。

    3. 并发操作:当多个并发请求需要修改同一份数据时,需要使用事务来保证数据的一致性。例如,多个用户同时对同一商品进行抢购,需要使用事务来控制商品库存的减少和订单的生成。

    4. 异常处理:当方法执行过程中发生异常,需要回滚之前的操作,可以使用事务来实现回滚。例如,用户购买商品时,如果支付过程中发生异常,需要回滚订单和库存的操作。

    总之,一般需要在涉及到数据库操作、多个服务方法调用、并发操作和异常处理的场景下使用事务。事务可以确保操作的一致性和完整性,提供可靠的数据访问和更新机制。

    • 步骤

      1. 开启事务:在执行一组操作之前,通过调用数据库的事务管理器来开启一个事务。

      2. 执行操作:在事务中执行一组相关的操作,如插入、更新、删除等。

      3. 提交事务:如果所有操作都成功执行,通过调用事务管理器的提交方法来提交事务,将操作的结果永久保存到数据库中。

      4. 回滚事务:如果有任何操作失败或发生异常,通过调用事务管理器的回滚方法来回滚事务,撤销已经执行的操作,保持数据库的一致性。

    • .spring支持编程式事务声明式事务是实现事务管理的两种主要方式。(重点)**

      1. 编程式事务:编程式事务是通过编写代码来管理事务的方式。在编程式事务中,开发人员需要手动编写事务的开启、提交和回滚等逻辑。通常,编程式事务使用的是底层的事务管理API,如JDBC的事务管理API。以下是一个使用编程式事务的示例:

      Connection conn = null;
      try {
          conn = dataSource.getConnection();
          conn.setAutoCommit(false); // 开启事务
          // 执行数据库操作
          // ...
          conn.commit(); // 提交事务
      } catch (SQLException e) {
          if (conn != null) {
              try {
                  conn.rollback(); // 回滚事务
              } catch (SQLException ex) {
                  ex.printStackTrace();
              }
          }
          e.printStackTrace();
      } finally {
          if (conn != null) {
              try {
                  conn.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      }
      1. 声明式事务:声明式事务是通过配置的方式来管理事务的方式。在声明式事务中,开发人员只需要在配置文件或注解中声明事务的属性,框架会自动根据配置来管理事务。常见的使用声明式事务的框架有Spring的事务管理机制。以下是一个使用声明式事务的示例:

      @Transactional
      public void doTransaction() {
          // 执行数据库操作
          // ...
      }

      在上述示例中,使用了@Transactional注解来声明事务,并将事务逻辑封装在doTransaction方法中。框架会根据注解的配置来管理事务的开启、提交和回滚等操作。

      总的来说,编程式事务需要手动编写事务管理的代码,灵活性较高,但工作量较大;而声明式事务通过配置的方式来管理事务,简化了开发人员的工作,但灵活性相对较低。选择使用哪种方式取决于具体的需求和项目的特点。

    5.Spring事务失效的场景(都是代码执行出现异常,事务却没有回滚)

    • 异常捕获处理,自己处理了异常,没有抛出,解决:手动抛出

    • 抛出检查异常,配置rollbackFor属性为Exception

    • 非public方法导致的事务失效,改为public

    详细解释:

    第一种:异常捕获处理,自己处理了异常,没有抛出,解决:手动抛出(左图改成右图)

                        事务失效的情况                                                                  改正后

    第二种:抛出检查异常,配置rollbackFor属性为Exception

    第三种:public方法导致的事务失效,改为public

    6.spring中bean的生命周期

    7.三级缓存 (具体解决流程看视频解析)

    可以解决spring中注入引起循环依赖问题

    构造方法中的循环依赖问题需要配合@Lazy懒加载注解解决

    8.spring中的常用注解

  • 相关阅读:
    搭建mysql8集群——一主一从
    【PowerQuery】PowerQuery导入JSON数据
    求因子数量
    22 【事件监听】
    Linux Debian12使用git将本地项目打标签、创建分支和分支合并到master再上传到码云(gitee)远程仓库
    【云栖2023】姜伟华:Hologres Serverless之路——揭秘弹性计算组
    AI学习指南机器学习篇-随机森林原理
    JL653—一个基于ARINC653的应用程序仿真调试工具
    Python入门自学进阶-Web框架——24、DjangoAdmin项目应用-定制页面2
    ViLBERT—(NeurIPS-2019)
  • 原文地址:https://blog.csdn.net/zj5566778899/article/details/132630775