IOC

IoC(Inversion of Control:控制反转):即控制反转/反转控制。它是一种思想不是一个技术实现

  • 控制:指的是对象创建(实例化、管理)的权力

  • 反转:控制权交给外部容器(Spring 框架、IoC 容器)

两方(创建对象的类和被创建的类)依赖被解耦,交给第三方容器来管理

好处

将程序员从复杂的依赖关系中解放出来,不用考虑对象是如何被创建出来的。

spring实现IOC

Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。 Spirng通过 IoC 容器来帮助我们实例化对象

IoC 解决了什么问题?

IoC 的思想就是两方(创建对象的类和被创建的类)之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?

  1. 对象之间的耦合度或者说依赖程度降低;

  2. 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。

IoC 和 DI 有区别吗?

IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。

@Autowired 依赖注入DI

依赖注入(DI)是Spring核心特性之一,而@Autowired也是我们日常高频使用的Spring依赖注入方式之一

image-20240419205254351

工作流程:当我们在代码中使用@Autowired时, IOC容器会自动查找并创建符合要求的Bean对象,将它注入到当前类

作用: 不需要我们自己捋 类与类之间的复杂依赖关系,简化了依赖注入的过程

springboot自动装配

image-20240313210703486


没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦

自动装配可以做到,我们引入一个第三方组件时,能够通过自动装配把组件自定义的配置 自动注入IOC容器生成bean

这样,我们就不需要手动配置和初始化每个组件依赖,从而简化了开发

如何实现自动装配

先看一下 SpringBoot 的核心注解 SpringBootApplication

可以把 @SpringBootApplication看作是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的集合

@EnableAutoConfiguration:启用 SpringBoot 的自动配置机制

@Configuration:允许在上下文中注册额外的 bean 或导入其他配置类

@ComponentScan:扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类

@EnableAutoConfiguration 是实现自动装配的重要注解,我们以这个注解入手。

自动装配的原理

是springboot为外部jar包提供的一套规范,按照规范就能把自己的功能快速整合进springboot项目

1.先扫描:

@EnableAutoConfiguration (开启自动装配) 在springboot启动时 扫描所有存在META-INF/spring.factories的jar包

2.再注入

将META-INF/spring.factories中的配置类配置信息加载到spring容器中,执行类中定义的操作

image-20240313205658033

注解

注解的本质就是一个继承了 Annotation 接口的接口。

一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。

而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。反射的事情我们待会说,而编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。

典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。

Bean

Bean 代指的就是那些被 IoC 容器所管理的对象。

声明为bean的方式:XML 文件、注解或者 Java 配置类。

将一个类声明为 Bean 的注解

@Component:通用的注解,可标注任意类为 Spring 组件。

@Repository@Service@Controller

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

@Component 注解作用于类,而@Bean注解作用于方法

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
​
}

注入 Bean 的注解有哪些

Spring 内置的 @Autowired 以及 JDK 内置的 @Resource@Inject 都可以用于注入 Bean。

作用域

Spring 中 Bean 的作用域通常有下面几种:

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。

  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。

  • 。。。

Bean 是线程安全的吗?

单例作用域且 bean内有可变成员变量,就会有线程安全问题

我们这里以最常用的两种作用域 prototype 和 singleton 为例介绍。几乎所有场景的 Bean 作用域都是使用默认的 singleton ,重点关注 singleton 作用域即可。

prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。

对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:

  1. 在 Bean 中尽量避免定义可变的成员变量。

  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)

bean的生命周期

Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。

读取配置定义载入到IOC容器中,在getbean时会根据配置定义利用反射 实例化bean,set属性赋值,初始化,使用,销毁(实例化,属性赋值,初始化前后都会提供扩展增强)

1.定义Bean配置信息、2.ResourceLoader定位配置信息文件,BeanDefinitionReader解析配置信息并注册BeanDefintion到容器Map中,3. 在我们程序运行时getBean获取Spring Bean又会经过Bean的实例化、属性赋值、初始化等环节,在这些环节前后,Spring也给了我们一些扩展机会,例如实例化前后、属性赋值前、初始化前、初始化后。因为Spring 单例Bean的生命周期是交给容器去管理的,所以Bean的销毁最后也依赖于容器的销毁,4. 当容器发出销毁消息时,会触发Bean的销毁逻辑,这是我们也可以在Bean销毁前做一些自定义操作

IOC容器关闭前才会触发bean的销毁, 不求同年同日生,但求同年同日G)

什么时候实例化?

已知bean是单例作用域,如果使用懒加载,那么就是在调用getbean时实例化,如果不使用的话,就在容器启动时实例化

image-20240419205308235

image-20240419205316073

IOC容器初始化流程

image-20240419205322358

  • 初始化的入口在容器实现中的 refresh()调用来完成

  • 对 bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition,其中的大致过程如下:

    • 通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader 是默认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统, URL 等方式来定为资源位置

    • 通过 BeanDefinitionReader来完成定义信息的解析获取beandefinition, 往往使用的是XmlBeanDefinitionReader 来解析 bean 的 xml 定义文件 - 实际的处理过程是委托给 BeanDefinitionParserDelegate 来完成的,从而得到 bean 的定义信息,这些信息在 Spring 中使用 BeanDefinition 对象来表示 - 这个名字可以让我们想到loadBeanDefinition,RegisterBeanDefinition 这些相关的方法 - 他们都是为处理 BeanDefinitin 服务的

    • 容器解析得到 BeanDefinition 以后,通过Register接口注册它到IOC容器中的一个hashmap中,

      这由 IOC 实现 BeanDefinitionRegistry 接口来实现。注册过程就是在 IOC 容器内部维护的一个HashMap 来保存得到的 BeanDefinition 的过程。这个 HashMap 是 IoC 容器持有 bean 信息的场所,**以后对 bean 的操作都是围绕这个HashMap 来实现的.

  • 然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了,在使用 IOC 容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IoC 风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。 Spring 本身提供了对声明式载入 web 应用程序用法的应用程序上下文,并将其存储在ServletContext 中的框架实现。

如果作为一个IOC容器的设计者,主体上应该包含哪几个部分:

  • 加载Bean的配置(比如xml配置)

    • 比如不同类型资源的加载,解析成生成统一Bean的定义

  • 根据Bean的定义加载生成Bean的实例,并放置在Bean容器中

    • 比如Bean的依赖注入,Bean的嵌套,Bean存放(缓存)等

  • 对容器中的Bean提供统一的管理和调用

    • 比如用工厂模式管理,提供方法根据名字/类的类型等从容器中获取Bean

  • ...

基本概念

  • BeanFactory: 工厂模式定义了IOC容器的基本功能规范

  • BeanRegistry: 向IOC容器手工注册 BeanDefinition 对象的方法(注册beandefintion到容器中)

  • BeanDefinition 定义了各种Bean对象及其相互的关系

  • BeanDefinitionReader 这是BeanDefinition的解析器 (解析获得beandefiniton)

  • ApplicationContext表示的是应用的上下文,对Bean的管理

AOP

AOP(Aspect-Oriented Programming:面向切面编程)

能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

原理:

基于动态代理(反射)。

AOP 为什么叫面向切面编程?

AOP 之所以叫面向切面编程,是因为它的核心思想就是将横切关注点从核心业务逻辑中分离出来,形成一个个的切面(Aspect)

AOP核心流程

这里顺带总结一下 AOP 关键术语(不理解也没关系,可以继续往下看):

  • 横切关注点(cross-cutting concerns) :多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等)。

  • 切面(Aspect):对横切关注点进行封装的类,一个切面是一个类。切面可以定义多个通知,用来实现具体的功能。

  • 连接点(JoinPoint):连接点是方法调用或者方法执行时的某个特定时刻(如方法调用、异常抛出等)。

  • 通知(Advice):通知就是切面在某个连接点要执行的操作。通知有五种类型,分别是前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。前四种通知都是在目标方法的前后执行,而环绕通知可以控制目标方法的执行过程。

  • 切点(Pointcut):一个切点是一个表达式,它用来匹配哪些连接点需要被切面所增强。切点可以通过注解、正则表达式、逻辑运算等方式来定义。比如 execution(* com.xyz.service..*(..))匹配 com.xyz.service 包及其子包下的类或接口。

  • 织入(Weaving):织入是将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。常见的织入时机有两种,分别是编译期织入(AspectJ)和运行期织入(AOP

以日志记录为例进行介绍,假如我们需要对某些方法进行统一格式的日志记录,没有使用 AOP 技术之前,我们需要挨个写日志记录的逻辑代码,全是重复的的逻辑。

image-20231123153641342

使用 AOP 技术之后,我们可以日志记录的逻辑封装成一个切面,然后通过切入点和通知来指定在哪些方法需要执行日志记录的操作。

​
// 日志注解
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
​
    /**
     * 描述
     */
    String description() default "";
​
    /**
     * 方法类型 INSERT DELETE UPDATE OTHER
     */
    MethodType methodType() default MethodType.OTHER;
}
​
// 日志切面
@Component
@Aspect
public class LogAspect {
  // 切入点,所有被 Log 注解标注的方法
  @Pointcut("@annotation(cn.javaguide.annotation.Log)")
  public void webLog() {
    }
​
   /**
   * 环绕通知
   */
  @Around("webLog()")
  public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    // 省略具体的处理逻辑
  }
​
  // 省略其他代码
}
​

如图,我们是先自定义了一个log注解,用来标识需要作为切点的方法,在切面类中配置切点捕捉所用用了log注解标识的方法。通知用来处理具体的复用逻辑

这样的话,我们一行注解即可实现日志记录:

@Log(description = "method1",methodType = MethodType.INSERT)
public CommonResponse<Object> method1() {
      // 业务逻辑
      xxService.method1();
      // 省略具体的业务处理逻辑
      return CommonResponse.success();
}
​

实现原理

Spring AOP 就是基于动态代理的(动态代理生成代理类,可以在代理类中invoke方法,可以在方法执行前后增强),如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

image-20240419205332463


AOP最早是AOP联盟的组织提出的,指定的一套规范,spring将AOP的思想引入框架之中,通过预编译方式运行期间动态代理实现程序的统一维护的一种技术,

动态织入静态织入

  1. 动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术

  2. ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

SpringMVC

MVC 是一种设计模式,MVC即Model、View、Controller即模型、视图、控制器。思想是将软件用户界面和业务逻辑分离

Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发

Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。

Spring事务

什么是事务?

事务是逻辑上的一组操作,要么都执行,要么都不执行。

Spring 对事务的支持

编程式事务管理

通过 TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用

@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
​
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
​
                try {
​
                    // ....  业务代码
                } catch (Exception e){
                    //回滚
                    transactionStatus.setRollbackOnly();
                }
​
            }
        });
}
​

使用 TransactionManager 进行编程式事务管理

@Autowired
private PlatformTransactionManager transactionManager;
​
public void testTransaction() {
​
  TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
          try {
               // ....  业务代码
              transactionManager.commit(status);
          } catch (Exception e) {
              transactionManager.rollback(status);
          }
}
​

声明式事务管理

推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

Spring 事务管理接口

PlatformTransactionManager:(平台)事务管理器,Spring 事务策略的核心。

TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。

TransactionStatus:事务运行状态。

事务传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

举个例子:我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?

@Service
Class A {
    @Autowired
    B b;
    @Transactional(propagation = Propagation.xxx)
    public void aMethod {
        //do something
        b.bMethod();
    }
}
​
@Service
Class B {
    @Transactional(propagation = Propagation.xxx)
    public void bMethod {
       //do something
    }
}

1.TransactionDefinition.PROPAGATION_REQUIRED

使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:

  • 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

  • 如果外部方法开启事务并且被Propagation.REQUIRED的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。

如果bMethod()发生异常回滚,aMethod()会跟着回滚


2.TransactionDefinition.PROPAGATION_REQUIRES_NEW

创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

如果aMethod()发生异常回滚,bMethod()不会跟着回滚


还有几种事务传播行为....略

事务回滚规则

这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。

如果你想要回滚你定义的特定的异常类型的话,可以这样:

@Transactional(rollbackFor= MyException.class)

Spring AOP 自调用问题

当一个方法被标记了@Transactional 注解的时候,Spring 事务管理器只会在被其他类方法调用的时候生效,而不会在一个类中方法调用生效。

MyService 类中的method1()调用method2()就会导致method2()的事务失效。

@Service
public class MyService {
​
private void method1() {
     method2();
     //......
}
@Transactional
 public void method2() {
     //......
  }
}
​

@Transactional 的使用注意事项总结

  • @Transactional 注解只有作用到 public 方法上事务才生效,不推荐在interferce上使用;

  • 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;

  • 正确的设置 @TransactionalrollbackForpropagation 属性,否则事务可能会回滚失败;

  • @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;

  • 底层使用的数据库必须支持事务机制,否则不生效;