GenweiWu / Blog

个人技术能力提升
MIT License
4 stars 0 forks source link

2021_spring.mybatis #72

Open GenweiWu opened 3 years ago

GenweiWu commented 3 years ago

> spring题

为啥要使用spring/spring mvc

  1. 轻量级框架,内置tomcat容器
  2. 依赖反转IOC
  3. aop面向切面编程
  4. bean声明周期管理:scope等
  5. 第三方框架快速集成:数据库对接、redis对接、es对接
  6. 健康度检查

aop面向切面编程

传统的oop面向对象编程,三个特性:封装、继承、多态性,一般我们处理具体业务

但是,如果我们想加入一些功能,比如调用方法前后打印日志,或者是事务管理等,这种代码的特点是:它会遍布在我们代码的各个地方,同时跟业务没有强相关。

AOP的几个控制点

基本概念

@Pointcut("execution(* com.joonwhee.open.demo.service..*.*(..))")
    public void pointcut() {
    }

控制反转IOC

以前,有个A类想调用B类的方法,那A要自己去创建B都实例,比如每次调用前new B出来然后在调用,甚至是单例模式去创建B出来;

spring通过IOC容器功能,现在A想调用B类,就只要Autowired B b就可以了,此时A类只要告诉spring它要调用B,B的创建时由spring来控制的

spring常见的注入方法

  1. 属性注入
public class TestBean12 {  
    @Autowired //字段注入  
    private String message;  
    //省略getter和setter  
} 
  1. 构造器注入
@Autowired //构造器注入  
private TestBean11(String message) {  
   this.message = message;  
} 
  1. set方法注入
 private String message;  
    @Autowired //setter方法注入  
    public void setMessage(String message) {  
        this.message = message;  
    } 

spring中的bean是否线程安全?

我理解sping本身是没有直接提供线程安全保证的;

这个问题要具体分析:

比如

spring 事务实现方式有哪些?

  1. 基于注解方式
@Transactional(propagation=Propagation.REQUIRED)
public xxx

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

  1. 手工管理
transactionTemplate.execute
  1. 基于注解等等

spring事务失效场景

1. @Transtional加在非public方法上

2. final方法

事务要依靠aop代理,final导致无法重写改方法


@Service
public class UserService {
@Transactional
public final void add(UserModel userModel){
    saveData(userModel);
    updateData(userModel);
}

}


### 3. 内部方法1调用了方法2

> 非Transtional调用Transaction方法,后者事务失效
```java
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
       //由于这里是直接通过this调用的,导致无法使用代理类进行替换,事务失效
        updateStatus(userModel);
    }

    @Transactional
    public void updateStatus(UserModel userModel) {
        doSameThing();
    }
}

如果两个方法都有Transactional注解,感觉是可以的,因为此时内部方法updateOrder有没有注解无所谓了

@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order); 
}
@Transactional()
public void updateOrder(Order order) {
// update order;
}
}

但是如果内部的方法Transactional希望有新的事务,则新事务失效

@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order); 
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
// update order;
}
}

修复方法是

方法1:新增一个serviceB进行调用


@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;

public void save(User user) { queryData1(); queryData2(); serviceB.doSave(user); } }

@Servcie public class ServiceB {

@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
   addData1();
   updateData2();
}

}


> 方法2:新增一个对自己的引用
```java
@Servcie
public class ServiceA {
   @Autowired
   prvate ServiceA serviceA;

   public void save(User user) {
         queryData1();
         queryData2();
         serviceA.doSave(user);
   }

   @Transactional(rollbackFor=Exception.class)
   public void doSave(User user) {
       addData1();
       updateData2();
    }
 }

4. 异常被吃了

@Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order;
         }catch (Exception e){
            //do something;
        }
    }

5. 异常类型不匹配

比如Transactional默认异常我RuntimeException,但是代码里我们抛出了其他的异常
此时需要人为指定异常

@Service
public class OrderServiceImpl implements OrderService {
 @Transactional
   // 默认异常是RuntimeException
 // @Transactional(rollbackFor = SQLException.class)
    public void updateOrder(Order order) {
        try {            // update order
          }catch (Exception e){
           throw new Exception("更新错误");        
        }    
    }
}

spring 的事务隔离?

使用的默认值,应该是

使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是: READ_COMMITTED

spring mv的请求过程

https://blog.csdn.net/qq_41701956/article/details/103253168

  1. 请求首先到达dispatchServlet
  2. 查找对应的handlerMapping找到对应的handle和拦截器
  3. 找到handler对应的handlerAdapter,进行参数映射等一些动作,最终去执行handler中的对应方法
  4. handler执行完方法,返回ModelAndView对象给DispatchServlet
  5. 根据modelAndView找到对应的ViewResolver,进行视图渲染
  6. 将渲染结果染回给客户端

@Import的作用

https://zhuanlan.zhihu.com/p/147025312

1. import另一个Configuration

@Import(ConfigB.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}

@Configuration
class ConfigB {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceB() {
        return new ServiceB();
    }
}

2. 直接引入另一个Bean

在Spring 4.2之后,@Import可以直接指定实体类,加载这个类定义到context中。 例如把上面代码中的ConfigA的@Import修改为@Import(ServiceB.class),就会生成ServiceB的Bean

3. 指定实现ImportSelector

...没咋用过...

GenweiWu commented 3 years ago

> Mybatis题

https://thinkwon.blog.csdn.net/article/details/101292950

什么是ORM

ORM(Object relational Mapping)是对象关系映射,用来将关系型数据库的数据和java Bean进行映射,将程序对象保存到数据库中去

什么是Mybatis

Mybatis是优秀的持久层框架,是半自动的ORM框架

它支持定制sql语句、存储过程、高级的字段映射

JDBC有哪些缺点?Mybatis又是如何解决的?

  1. 需要频繁建立数据库连接,以及释放数据库连接;建议使用数据库连接池;

    Mybatis可以配置数据库连接池,也不用显示的每次去获取数据库连接,释放连接

  2. sql语句是通过字符串的方式写在java代码里的,查看维护都很麻烦;如果再遇到条件判断ifelse,就更加复杂

    Mybatis是将sql语句写在xml文件中的,方便维护管理

  3. 向sql语句传参数很麻烦,展位附近要考虑下标是0,1这种

    Mybatis是自动映射,可以通过将javaBean和sql中的字段映射起来

  4. sql返回结果需要遍历resultSet,无法自动映射为POJO(javaBean)

    Mybatis可以自动将结果映射为java bean

Mybatis优缺点

优点

相对于传统JDBC:

  1. 大大减少代码量
  2. 提供数据库连接池避免资源浪费
  3. xml文件中写sql,便于维护管理
  4. 可以进行参数和结果字段映射
  5. 可支持sql片段(sql),以及sql片段复用(include)
  6. 可以方便的跟spring进行集成

缺点:

相对于Hibernate:

  1. 还是要写大量的sql语句
  2. 跟数据库类型绑定的,切换数据库需要修改sql语句进行兼容

Mybatis适用场景

  1. 业务比较复杂,逻辑相对复杂,适合手写sql语句
  2. 专注于sql本身,可以提供灵活高效的持久层方案

Hibernate 和 MyBatis 的区别

Hibernate Mybatis
全自动的ORM框架 半自动的ORM框架
支持数据库无关性,切换数据库自动兼容 切换数据库要修改sql
门槛高,适合需求变化少,逻辑简单的系统 门槛低,适合复杂逻辑系统

Mybatis工作原理

  1. 读取mybatis数据库配置文件,初始化数据库连接池
  2. 加载mapper.xml类型文件
  3. 创建sqlSessionFactory
  4. 使用sqlSessionFactory创建sqlSession,包含了执行sql语句所有方法
  5. Executor执行器,根据sqlSession的内容生成sql语句,并负责维护查询缓存
  6. MappedStatement对参数和返回结果进行映射:比如Map、List类型、基本数据类型、POJO类型

为什么要预编译

1. 什么是预编译?

数据库驱动将sql语句+参数发送给DBMS(数据库管理系统,比如Mysql)前先进行编译,DBMS收到sql后就不用再次编译

2.为什么要预编译?

Mybatis都有哪些Executor执行器?

  1. SimpleExecutor 每次执行都创建一个新的statement,用完就关闭
  2. ReuseExecutor 有就复用,没有就新建一个statement,使用后不关闭,放到Map<String, Statement>内复用
  3. BatchExecutor 批处理

Mybatis是否支持延迟加载(了解)

支持,通过代理模式进行懒加载

https://juejin.cn/post/6844904062362583054 主要是利用association的column指定查询id,设置select内容来指定延迟查询的sql的id


<mapper namespace="cn.ideal.mapper.AccountMapper">
<!-- 定义封装 Account和User 的resultMap -->
<resultMap id="userAccountMap" type="Account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 配置封装 User 的内容
select:查询用户的唯一标识
column:用户根据id查询的时候,需要的参数值
-->
<association property="user" column="uid" javaType="User" select="cn.ideal.mapper.UserMapper.findById"></association>
</resultMap>
<!-- 根据查询所有账户 -->
<select id="findAll" resultMap="userAccountMap">
    SELECT * FROM account
</select>


### #{}和${}的区别

- #会进行预编译处理,能防止sql注入问题;$是直接替换字符串,有注入问题
- #相当于PreparedStatement处理,$相当于Statement处理
- #对应的变量会自动加上单引号,$不会

## 模糊查询

`concat('%',#{xxx},'%')`

### 在mapper中如何传递多个参数

1. #{0} #{1}
2.  使用`@Param("xxx")`参数,直接写参数名`xxx`  ***
3.  使用Map传递
4.  使用javabean传递   ***

## 如何批量执行

1. 使用foreach,主要用在

`insert into tableA value (1,11),(2,22) `

2. 自己创建sqlSessionFactory.openSession(ExecuteType.BATCH)

## java bean的属性和数据库表字段不匹配如何解决?

1. 使用as进行转换:select a_b as ab ...
2.  使用ResultMap

```xml
<ResultMap>
  <result property="ab" column="a_b"></result>
</ResultMap>

使用Mybatis的mapper接口调用时由哪些要求?

  1. maper.xml中namespace 等于 mapper接口类路径
  2. mapper接口中的方法名 = mapper.xml文件中sql语句的id
  3. 参数类型和数量要匹配
  4. 返回结果要匹配

Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

区分是 namespace+id进行区分

所以相同namespace的话id不能重复;不同的namespace的话id可以重复

xml映射文件中的常用标签

  1. update、insert、delete、select
  2. sql片段、include包含片段
  3. parameterMap和resultMap进行参数和结果映射
  4. 条件判断的
where
if 
choose
when 
otherwise
foreach
<select id="dynamicChooseTest" parameterType="Blog" resultType="Blog">
        select * from t_blog where 1 = 1 
        <choose>
            <when test="title != null">
                and title = #{title}
            </when>
            <when test="content != null">
                and content = #{content}
            </when>
            <otherwise>
                and owner = "owner1"
            </otherwise>
        </choose>
    </select>

动态sql如何实现

OGNL表达式从sql参数中计算出表达式的值,然后根据表达值进行动态拼接

Mybatis是否可以映射枚举类

可以,自定义实现TypeHandler,实现 setParameter方法和getResult方法

Mybatis二级缓存

https://github.com/GenweiWu/Blog/blob/master/db/mybatis/%E4%B8%80%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98.md

  1. 一级缓存,基于session
  2. 二级缓存,基于namespace

没使用缓存,分布式环境下会有脏数据的

GenweiWu commented 3 years ago

SpringBoot

Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

@SpringBootApplication 包括

  1. @SpingBootConfiguration:就是spring中的@Configuration,类似于xml配置
  2. @EnableAutoConfiguration:打开自动配置,比如配置了数据库信息就会自动生成数据库连接对象
  3. @ComponentScan:spring组件扫描,比如@Bean就给你生成一个bean对象

@Bean 和 @Component(@Service,@Repository)

1. 两者都可以被@Autowired、@Resource注入

2. 区别:@Component 作用于类,@Bean作用于方法。

@Component一般直接写在类上面,就可以生成一个bean,而有时候你想生成一个第三方代码的对象,又不能在代码上写@Component,可以利用@Bean,此时@Bean对应一个方法,方法都返回对象就是一个bean,而且你可以做一些定制

@Bean则常和@Configuration注解搭配使用:

@Configuration
public class WebSocketConfig {
    @Bean
    public Student student(){
        return new Student();
    }
}   

比如应用在解决spring session的问题

/**
* Spring session 浏览器sessionId与服务器不一致解决方案
*/
@Bean
public DefaultCookieSerializer getDefaultCookieSerializer()
{
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
defaultCookieSerializer.setUseBase64Encoding(false);
return defaultCookieSerializer;
}

Spring Boot 有哪些优点?

  1. 上手容易,开箱即用,很多默认配置
  2. bean生命周期管理
  3. 内嵌tomcat容器,提供健康度检查,提供endpoint能力
  4. 替换xml配置,使用javaconfi配置,可以灵活动态加载组件
  5. 提供众多组件,比如注册发现中心,分布式配置configServer,不用重头搭建

javaconfig是什么?

以前spring配置是用xml配置,现在是使用java代码来实现之前xml的配置能力 比如生成bean使用注解

使用javaconfig的好处

  1. 减少xml配置,减少xml和java代码的切换,以前生成一个bean,既要xml中配置<bean,又要java中写代码,后期如果删除啥的也要同步

  2. 面向对象的配置,你可以去继承一个类,修改一些属性来覆盖之前的bean的一些配置

springBoot读取配置

可以从以下位置读取配置:

  1. properties文件
  2. yml文件
  3. 系统环境变量
  4. 命令行参数

优先级:4>3>1=2

yaml

什么是yaml?

类似于properties,用来作为配置文件配置一些参数供系统读取

yaml的优势

  1. 有序:多个相关配置,放在一起
  2. 简洁:对比类似json文件,配置文件很简洁
  3. 支持多种数据类型:比如字符串,数组,map类型(map跟object差不多)
env:
    list:
        - name: '1001'
          type: all
        - name: '1003'
          type: all

springBoot是否可以使用xml配置?

@ImportResource注解

@ImportResource("classpath:beans.xml")

spring boot 核心配置文件

  1. bootstrap.yml 1.1 由父ApplicationContext加载,先于application.yml加载 1.2 属性不会被覆盖

  2. application.yml 2.1 由applicationContext加载 2.2 内容可以被覆盖,一般应用的配置放在这里,可以被配置中心的配置覆盖

什么是 Spring Profiles?

1.启用指定的profile

-Dspring.profiles.active=test

2. 不同profiles的配置

---

spring:
  profiles: test

server:
  port: 8000
---

spring:
  profiles: production

server:
  port: 80

pebble:
  cache: true

另外,java代码都Component和@Configuration也可以使用@Profile

@Configuration
@Profile("production")
public class ProductionConfiguration {

    // ...

}

自定义端口

server.port = 9090

springBoot中的监视器

springBoot提供的一些现成的endpoint,用来分析、监控当前应用的信息,健康情况等

management:
  endpoints:
    web:
      exposure:
        include: info, health, env, metrics

加载配置文件

  1. @Value("aa.bb")

  2. @ConfiturationProperties

    @Configuration
    @ConfigurationProperties("storage.local")
    public class StorageConfiguration {
    ...
    }

websocket

  1. websocket可以双向通信
  2. websocket是全双工的,客户端和服务器都可以发送消息 全双工类比打电话,半双工类比对讲机
  3. 比http轻量

spring data

spring模块,主要用来进行数据库访问,也包括Nosql服务访问

比如spring-data-redis、spring-data-es

集成FreeMarker模板

基于java的模板引擎

比如我们用来做邮件模板,邮件的模板是固定的,但是有一些数据是动态的,可以通过freeMarker加载模板,同时动态传入参数,最后渲染出来静态html

集成kafka

https://www.liaoxuefeng.com/wiki/1252599548343744/1282388443267106

消息通信服务器 依赖zookeeper

集成swagger

生成可视化api文档

Spring Boot 中的 starter 到底是什么 ?

我理解就是springBoot提供的对于某个功能的封装打包;

比如spring-boot-starter-data-redis就是提供redis访问的一些能力;

starter的特点是:

  1. 提供自动配置能力,通常叫做xxxAutoConfiguration,可以通过判断是否配置了redis的相关配置,自动去生成对应的redisTemplate对象
  2. 提供 Production-Ready 特性,即健康检查等能力(比如dubbo-spring-boot-actuator )
  3. 提供了对应的依赖信息,比如spring-data-redis的底层包,这样就不需要开发人员自己去找齐所以的依赖

一些spring-boot-starter了解:

spring-cloud-starter-openfeign
spring-cloud-starter-netflix-eureka-client
spring-cloud-starter-config
spring-boot-starter-actuator
spring-boot-starter-aop
spring-boot-starter
spring-boot-starter-web
spring-boot-starter-data-redis
spring-cloud-starter-zipkin
mybatis-spring-boot-starter
spring-boot-starter-freemarker  

spring-boot-starter-parent有什么用?

  1. 整合依赖关系(继承自 spring-boot-dependencies

  2. 定义编码为utf-8

  3. 指定jdk版本为jdk1.8

    <properties>
    <java.version>1.8</java.version>
    <resource.delimiter>@</resource.delimiter>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
  4. 自动化的资源过滤,

    <resources>
    <resource>
    <directory>${basedir}/src/main/resources</directory>
    <filtering>true</filtering>
    <includes>
      <include>**/application*.yml</include>
      <include>**/application*.yaml</include>
      <include>**/application*.properties</include>
    </includes>
    </resource>
    <resource>
    <directory>${basedir}/src/main/resources</directory>
    <excludes>
      <exclude>**/application*.yml</exclude>
      <exclude>**/application*.yaml</exclude>
      <exclude>**/application*.properties</exclude>
    </excludes>
    </resource>
    </resources>
  5. 自动化的插件管理

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <configuration>
    <archive>
      <manifest>
        <mainClass>${start-class}</mainClass>
        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
      </manifest>
    </archive>
  </configuration>
</plugin>

springboot打包的jar和普通的jar的区别

  1. 前者可运行,不可被依赖
  2. 后者被依赖调用,不可以直接执行

运行springBoot的方式

  1. 开发main函数直接启动
  2. 现网,java -jar执行
  3. 达成war包放到tomcat容器中

springBoot需要独立的web容器吗?

不需要,内置了tomcat\jetty容器

springBoot异常处理

实现一个ControllerAdvice类,定义ExceptionHandler来处理

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler()
    @ResponseBody
    String handleException(Exception e){
        return "Exception Deal! " + e.getMessage();
    }
}

spring-boot分布式会话

redis

spring-boot分部署定时器

quartz定时器

三个概念

https://blog.csdn.net/noaman_wgs/article/details/80984873

1. job,定义定时器定时去做的工作内容

2. trigger触发器,定义执行规则

2.1 SimpleTrigger

定时器开始时间、结束时间、多久执行一次、执行多少次停止

  Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
                .usingJobData("trigger1", "这是jobDetail1的trigger")
                .startNow()//立即生效
                .startAt(startDate)
                .endAt(endDate)
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(1)//每隔1s执行一次
                .repeatForever())
                .build();//一直执行

2.2 CronTrigger

每天几点定时执行 0 0 2 * * ? 每天2点定时执行

CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
                .usingJobData("trigger1", "这是jobDetail1的trigger")
                .startNow()//立即生效
                .startAt(startDate)
                .endAt(endDate)
                .withSchedule(CronScheduleBuilder.cronSchedule("* 30 10 ? * 1/5 2018"))
                .build();

3. scheduler

用来将job和trigger关联起来,并且可以启动和停止定时器

scheduler.start();


```java
 // 暂停触发器的计时
scheduler.pauseTrigger(trigger.getKey());
// 移除触发器中的任务
scheduler.unscheduleJob(trigger.getKey());
// 删除任务
scheduler.deleteJob(jobDetail.getKey());

springBoot中quartz

 * 注意JobDetail和Trigger是org.quartz包下的,不是spring包下的,不要导入错误
 */
@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail jobDetail() {
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("start_of_day", "start_of_day")
                .storeDurably()
                .build();
        return jobDetail;
    }

    @Bean
    public Trigger trigger() {
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail())
                .withIdentity("start_of_day", "start_of_day")
                .startNow()
                // 每天0点执行
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?"))
                .build();
        return trigger;
    }
}
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class HelloJob extends QuartzJobBean
{
    @Override
    protected void executeInternal(JobExecutionContext context)
    {
        System.out.println("hello: " + new Date());
    }
}

quartz 中的数据库表

1.qrtz_blob_triggers 2.qrtz_cron_triggers -- cron类型的trigger 3.qrtz_simple_triggers --simple类型的trigger 4.qrtz_simprop_triggers 5.qrtz_fired_triggers --正在运行的trigger,跑完会删除 6.qrtz_triggers 7.qrtz_job_details --job的具体信息 8.qrtz_calendars 9.qrtz_paused_trigger_grps 10.qrtz_scheduler_state --scheduler的状态表 11.qrtz_locks

GenweiWu commented 3 years ago

Spring三级缓存

1,2级缓存用来解决:循环依赖

3级缓存用来优化aop代理可能会生成代理对象

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    ...
    // 从上至下 分表代表这“三级缓存”
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
    ...

    /** Names of beans that are currently in creation. */
    // 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
    // 它在Bean开始创建时放值,创建完成时会将其移出~
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    /** Names of beans that have already been created at least once. */
    // 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
    // 至少被创建了一次的  都会放进这里~~~~
    private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
}

循环依赖的解决

spring的循环依赖只有当

  1. Bean是单例
  2. 通过属性注入方式

如果是构造器注入、或者不是单例模式的情况下,会出现异常

构造器注入或者prototype类型的属性注入,都会导致Bean创建失败