1.Spring的生命周期

一个受 Spring 管理的 bean,生命周期主要阶段

  1. 创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象

  2. 依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系

  3. 初始化:回调各种 Aware 接口,调用对象的各种初始化方法

  4. 销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)

  • prototype 对象也能够销毁,不过需要容器这边主动调

一些资料会提到,生命周期中还有一类 bean 后处理器:BeanPostProcessor,会在 bean 的初始化的前后,提供一些扩展逻辑。但这种说法是不完整的

更加详细的过程如下:

创建前后的增强

  • postProcessBeforeInstantiation

  • 这里返回的对象若不为 null 会替换掉原本的 bean,并且仅会走 postProcessAfterInitialization 流程

  • postProcessAfterInstantiation

  • 这里如果返回 false 会跳过依赖注入阶段

依赖注入前的增强

  • postProcessProperties

  • 如 @Autowired、@Value、@Resource

初始化前后的增强

  • postProcessBeforeInitialization

  • 这里返回的对象会替换掉原本的 bean

  • 如 @PostConstruct、@ConfigurationProperties

  • postProcessAfterInitialization

  • 这里返回的对象会替换掉原本的 bean

  • 如代理增强

销毁之前的增强

2.Spring注解

2.1、声明bean的注解

@Component 组件,没有明确的角色

@Service 在业务逻辑层使用(service层)

@Repository 在数据访问层使用(dao层)

@Controller 在展现层使用,控制器的声明(C)

2.2、注入bean的注解

@Autowired:由Spring提供

@Inject:由JSR-330提供

@Resource:由JSR-250提供

都可以注解在set方法和属性上,推荐注解在属性上(一目了然,少写代码)。

2.3、java配置类相关注解

  • @Configuration 声明当前类为配置类,相当于xml形式的Spring配置(类上)

  • @Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上)

  • @Configuration 声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个bean(类上)

  • @ComponentScan 用于对Component进行扫描,相当于xml中的(类上)

  • @WishlyConfiguration 为@Configuration与@ComponentScan的组合注解,可以替代这两个注解

2.4、切面(AOP)相关注解

Spring支持AspectJ的注解式切面编程。

@Aspect 声明一个切面(类上) 使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。

@After 在方法执行之后执行(方法上) @Before 在方法执行之前执行(方法上) @Around 在方法执行之前与之后执行(方法上)

@PointCut 声明切点 在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)

2.5、@Bean的属性支持

@Scope 设置Spring容器如何新建Bean实例(方法上,得有@Bean) 其设置类型包括:

Singleton (单例,一个Spring容器中只有一个bean实例,默认模式), Protetype (每次调用新建一个bean), Request (web项目中,给每个http request新建一个bean), Session (web项目中,给每个http session新建一个bean), GlobalSession(给每一个 global http session新建一个Bean实例)

@StepScope 在Spring Batch中还有涉及

@PostConstruct 由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod

@PreDestory 由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod

2.6、@Value注解

@Value 为属性注入值(属性上) 支持如下方式的注入: 》注入普通字符

@Value("Michel Jackson") 
String name; 

》注入操作系统属性

@Value("#{systemProperties['os.name']}") 
String osName; 

》注入表达式结果

@Value("#{ T(java.lang.Mathe).random()*10}") 
    String randomNuber; 

》注入其它bean属性

@Value("#{domeClass.name})
String name;

》注入文件资源

@Value("classpath:com.hgs/hello/test.txt") 
String resourceFile; 

》注入网站资源

@Value("http://www.fangzhipeng.com")
Resource url; 

》注入配置文件

@Value ("${book.name}")
String bookName;

注入配置使用方法:

book.name=《三体》

  • @PropertySource 加载配置文件(类上)

@PropertySource("classpath:com/hgs/hello/test.txt") 

还需配置一个PropertySourcesPlaceholderConfigurer的bean。

2.7、环境切换

@Profile 通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。(类或方法上)

@Conditional Spring4中可以使用此注解定义条件话的bean,通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。(方法上)

2.8、异步相关

@EnableAsync 配置类中,通过此注解开启对异步任务的支持,叙事性AsyncConfigurer接口(类上)

@Async 在实际执行的bean方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)

2.9、定时任务相关

@EnableScheduling 在配置类上使用,开启计划任务的支持(类上)

@Scheduled 来申明这是一个任务,包括cron,fixDelay,fixRate等类型(方法上,需先开启计划任务的支持)

2.10、@Enable*注解说明

这些注解主要用来开启对xxx的支持。 @EnableAspectJAutoProxy 开启对AspectJ自动代理的支持

@EnableAsync 开启异步方法的支持

@EnableScheduling 开启计划任务的支持

@EnableWebMvc 开启Web MVC的配置支持

@EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持

@EnableJpaRepositories 开启对SpringData JPA Repository的支持

@EnableTransactionManagement 开启注解式事务的支持

@EnableTransactionManagement 开启注解式事务的支持

@EnableCaching 开启注解式的缓存支持

2.11、测试相关注解

@RunWith 运行器,Spring中通常用于对JUnit的支持

@Runwith(SpringJunit4ClassRunner.class)

@ContextConfiguration 用来加载配置ApplicationContext,其中classes属性用来加载配置类

@ContextCongireation(classes={Testconfig.class})

3.Spring的后置处理器

BeanFactory 的后置处理器BeanPostProcessor自增强功能

每个后处理器各自增强什么功能

ConfigurationClassPostProcessor

可以解析

- @ComponentScan
- @Bean
- @Import
- @ImportResource

MapperScannerConfigurer

可以解析Mapper 接口

Bean 的后置处理器BeanPostProcessor自增强功能

每个后处理器各自增强什么功能

AutowiredAnnotationBeanPostProcessor

解析 @Autowired 与 @Value

CommonAnnotationBeanPostProcessor

解析 @Resource、@PostConstruct、@PreDestroy

ConfigurationPropertiesBindingPostProcessor

解析 @ConfigurationProperties

ContextAnnotationAutowireCandidateResolver

负责获取 @Value 的值,解析 @Qualifier、泛型、@Lazy 等

4.Spring循环依赖

要求

  • 掌握单例 set 方式循环依赖的原理

  • 掌握其它循环依赖的解决方法

循环依赖的产生

  • 首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱

  • set 方法(包括成员变量)的循环依赖如图所示

  • 可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决

  • 【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕

  • a 的顺序,及 b 的顺序都能得到保障

  • 构造方法的循环依赖如图所示,显然无法用前面的方法解决

构造循环依赖的解决

  • 思路1

  • a 注入 b 的代理对象,这样能够保证 a 的流程走通

  • 后续需要用到 b 的真实对象时,可以通过代理间接访问

  • 思路2

  • a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通

  • 后续需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问

  • 示例1:用 @Lazy 为构造方法参数生成代理

public class App60_1 {

    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private B b;

        public A(@Lazy B b) {
            log.debug("A(B b) {}", b.getClass());
            this.b = b;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B(A a) {
            log.debug("B({})", a);
            this.a = a;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();
        System.out.println();
    }
}
  • 示例2:用 ObjectProvider 延迟依赖对象的创建

public class App60_2 {

    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private ObjectProvider<B> b;

        public A(ObjectProvider<B> b) {
            log.debug("A({})", b);
            this.b = b;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B(A a) {
            log.debug("B({})", a);
            this.a = a;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();

        System.out.println(context.getBean(A.class).b.getObject());
        System.out.println(context.getBean(B.class));
    }
}
  • 示例3:用 @Scope 产生代理

public class App60_3 {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context.getDefaultListableBeanFactory());
        scanner.scan("com.itheima.app60.sub");
        context.refresh();
        System.out.println();
    }
}
@Component
class A {
    private static final Logger log = LoggerFactory.getLogger("A");
    private B b;

    public A(B b) {
        log.debug("A(B b) {}", b.getClass());
        this.b = b;
    }

    @PostConstruct
    public void init() {
        log.debug("init()");
    }
}
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
class B {
    private static final Logger log = LoggerFactory.getLogger("B");
    private A a;

    public B(A a) {
        log.debug("B({})", a);
        this.a = a;
    }

    @PostConstruct
    public void init() {
        log.debug("init()");
    }
}
  • 示例4:用 Provider 接口解决,原理上与 ObjectProvider 一样,Provider 接口是独立的 jar 包,需要加入依赖

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
public class App60_4 {

    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private Provider<B> b;

        public A(Provider<B> b) {
            log.debug("A({}})", b);
            this.b = b;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B(A a) {
            log.debug("B({}})", a);
            this.a = a;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();

        System.out.println(context.getBean(A.class).b.get());
        System.out.println(context.getBean(B.class));
    }
}

解决 set 循环依赖的原理

一级缓存

作用是保证单例对象仅被创建一次

  • 第一次走 getBean("a") 流程后,最后会将成品 a 放入 singletonObjects 一级缓存

  • 后续再走 getBean("a") 流程时,先从一级缓存中找,这时已经有成品 a,就无需再次创建

一级缓存与循环依赖

一级缓存无法解决循环依赖问题,分析如下

  • 无论是获取 bean a 还是获取 bean b,走的方法都是同一个 getBean 方法,假设先走 getBean("a")

  • 当 a 的实例对象创建,接下来执行 a.setB() 时,需要走 getBean("b") 流程,红色箭头 1

  • 当 b 的实例对象创建,接下来执行 b.setA() 时,又回到了 getBean("a") 的流程,红色箭头 2

  • 但此时 singletonObjects 一级缓存内没有成品的 a,陷入了死循环

二级缓存

解决思路如下:

  • 再增加一个 singletonFactories 缓存

  • 在依赖注入前,即 a.setB() 以及 b.setA() 将 a 及 b 的半成品对象(未完成依赖注入和初始化)放入此缓存

  • 执行依赖注入时,先看看 singletonFactories 缓存中是否有半成品的对象,如果有拿来注入,顺利走完流程

对于上面的图

  • a = new A() 执行之后就会把这个半成品的 a 放入 singletonFactories 缓存,即 factories.put(a)

  • 接下来执行 a.setB(),走入 getBean("b") 流程,红色箭头 3

  • 这回再执行到 b.setA() 时,需要一个 a 对象,有没有呢?有!

  • factories.get() 在 singletonFactories  缓存中就可以找到,红色箭头 4 和 5

  • b 的流程能够顺利走完,将 b 成品放入 singletonObject 一级缓存,返回到 a 的依赖注入流程,红色箭头 6

二级缓存与创建代理

“img/image-20210903103030877.png” could not be found.

二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下

  • spring 默认要求,在 a.init 完成之后才能创建代理 pa = proxy(a)

  • 由于 a 的代理创建时机靠后,在执行 factories.put(a) 向 singletonFactories 中放入的还是原始对象

  • 接下来箭头 3、4、5 这几步 b 对象拿到和注入的都是原始对象

三级缓存

“img/image-20210903103628639.png” could not be found.

简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 spring 仍然希望代理的创建时机在 init 之后,只有出现循环依赖时,才会将代理的创建时机提前。所以解决思路稍显复杂:

  • 图中 factories.put(fa) 放入的既不是原始对象,也不是代理对象而是工厂对象 fa

  • 当检查出发生循环依赖时,fa 的产品就是代理 pa,没有发生循环依赖,fa 的产品是原始对象 a

  • 假设出现了循环依赖,拿到了 singletonFactories 中的工厂对象,通过在依赖注入前获得了 pa,红色箭头 5

  • 这回 b.setA() 注入的就是代理对象,保证了正确性,红色箭头 7

  • 还需要把 pa 存入新加的 earlySingletonObjects 缓存,红色箭头 6

  • a.init 完成后,无需二次创建代理,从哪儿找到 pa 呢?earlySingletonObjects 已经缓存,蓝色箭头 9

当成品对象产生,放入 singletonObject 后,singletonFactories 和 earlySingletonObjects 就中的对象就没有用处,清除即可

5.Spring事务失效的几种场景及原因

案例演示:。。。。。。

6.Spring中用到的设计模式

单例模式

工厂模式

代理模式

模板方法模式

适配器模式

7.BeanFactory 和 ApplicationContext 有什么区别?

BeanFactory和ApplicationContext是Spring框架中的两个重要组件,它们在许多方面存在一些 区别:

  1. 加载方式:BeanFactory采用的是延迟加载注入bean,即只用使用到某个bean的时候,才对该Bean进行加载实例化。这意味着如果在启动时未使用某个bean,那么该bean不会被实例化,这有助于节省资源。而ApplicationContext在容器启动时,会一次性创建所有的bean,这有助于在后续程序使用过程中更加高效。

  2. 创建方式:BeanFactory通常以编程的方式创建bean,而ApplicationContext除了编程式创建外,还能以声明的方式创建bean。

  3. 注册方式:BeanFactory需要手动注册BeanPostProcessor和BeanFactoryPostProcessor,而ApplicationContext会自动注册。

  4. 国际化支持:ApplicationContext由于继承自MessageSource,因此可以支持国际化语言配置,这是BeanFactory所不具备的。

  5. 其他特性:ApplicationContext支持消息发送和响应机制以及AOP以及访问资源等,这些是BeanFactory不具备或实现起来更为复杂的功能。

总结来说,BeanFactory和ApplicationContext在加载方式、创建方式、注册方式、国际化支持和特性等方面存在区别。需要根据实际的应用需求来选择使用哪一个。

8.BeanFactory 和 FactoryBean 有什么区别?

BeanFactory和FactoryBean在Spring框架中都扮演着重要的角色,但它们之间存在一些明显的区别:

BeanFactory是Spring的核心,是IoC容器,主要负责创建、配置和管理bean。它提供了一种方法来支持外部程序对这些bean的访问,并且能在程序启动时根据传入的参数产生各种类型的bean,并添加到IOC容器中。BeanFactory有多种实现,如DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。

FactoryBean则是一个接口,其实现类首先是一个bean,也存放在BeanFactory中。FactoryBean主要负责生产和管理bean,是IoC容器的核心接口,它的职责包括实例化、定位、配置应用程序中的对象及建立这些对象的依赖。

总的来说,BeanFactory是IoC容器,负责创建、配置和管理bean,而FactoryBean则是一个负责生产和管理bean的工厂接口。

9.Spring中的Aware 接口和InitializingBean 接口

9.1. Aware 接口提供了一种【内置】 的注入手段,例如

  • BeanNameAware 注入 bean 的名字

  • BeanFactoryAware 注入 BeanFactory 容器

  • ApplicationContextAware 注入 ApplicationContext 容器

  • EmbeddedValueResolverAware 注入 ${} 解析器

9.2. InitializingBean 接口提供了一种【内置】的初始化手段

9.3. 对比

  • 内置的注入和初始化不受扩展功能的影响,总会被执行

  • 而扩展功能受某些情况影响可能会失效

  • 因此 Spring 框架内部的类常用内置注入和初始化

10.Bean初始化与销毁

Spring 提供了多种初始化手段,除了@PostConstruct,@Bean(initMethod) 之外,还可以实现 InitializingBean 接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的

初始化,执行顺序是

1. @PostConstruct 标注的初始化方法

2. InitializingBean 接口的初始化方法

3. @Bean(initMethod) 指定的初始化方法

销毁手段,执行顺序为

1. @PreDestroy 标注的销毁方法

2. DisposableBean 接口的销毁方法

3. @Bean(destroyMethod) 指定的销毁方法

@SpringBootApplication
public class A07_1 {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A07_1.class, args);
        context.close();
    }

    @Bean(initMethod = "init3")
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean(destroyMethod = "destroy3")
    public Bean2 bean2() {
        return new Bean2();
    }
}
public class Bean1 implements InitializingBean {
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    @PostConstruct
    public void init1() {
        log.debug("初始化1");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化2");
    }

    public void init3() {
        log.debug("初始化3");
    }
}
public class Bean2 implements DisposableBean {
    private static final Logger log = LoggerFactory.getLogger(Bean2.class);

    @PreDestroy
    public void destroy1() {
        log.debug("销毁1");
    }

    @Override
    public void destroy() throws Exception {
        log.debug("销毁2");
    }

    public void destroy3() {
        log.debug("销毁3");
    }
}

11,Spring作用域Scope

在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope

  • singleton,容器启动时创建(未设置延迟),容器关闭时销毁

  • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁

  • request,每次请求用到此 bean 时创建,请求结束时销毁

  • session,每个会话用到此 bean 时创建,会话结束时销毁

  • application,web 容器用到此 bean 时创建,容器停止时销毁

但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有