飞道的博客

spring 常用注解 原理 逻辑 代码演示

643人阅读  评论(0)

01 spring 常用注解 原理 逻辑 代码演示

这是自己观看视频的笔记

一、组件注册

1.1-spring注解驱动开发

1.2-组件注册 @Configuration

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.12.RELEASE</version>
</dependency>
  1. 如果创建beans.xml没有如下内容,则为没有添加spring支持

  1. 则开启

1.3-组件注册 Configuration、Bean、ComponentScan(s)、TypeFilter

  1. 创建一个Person类

  1. 配置beans.xml

  2. 给一个id方便从容器中获取

  3. 可以通过property作为一个属性的赋值[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RgFFaJDX-1590551049657)((https://cdn.jsdelivr.net/gh/1392517138/imgRepository@master/image-20200511220207948.png)]

这是以前的一个配置文件

  1. 开始使用,写一个测试类

    1. 通过ClassPathXmlApplicationContext,表示类路径下的一个xml配置文件。会返回IOC容器
    2. 可通过getBean加上“id”进行获取。或是类型

  2. 以前配置文件的方式被替换为了配置类

    1. 建立一个config.MainConfig

    2.回到MainTest,通过AnnotationConfigApplicationContext注解式的config,它传入的就是这个配置类。相当于是穿配置类的位置。

	3.	通过getBeanDefinitionNames可获得Bean容器中组件的所有名称

  1. 也可通过getBeanNamesForType

  1. 也可通过getBeanNamesForType

  1. 通过上面的这个方法,我也可改变组键名称。要么改方法名,要么采用下面这种方式

  1. 在实际开发中,包的扫描写得比较多

    1. 这是xml的写法(以前的方式)
    <!--包扫描、只要标注了@Controller@Service@Repository@Component,都会被自动扫描加入容器中-->
    <context:component-scan base-package="top.p3wj"></context:component-scan>
    

​ 3. 写在配置类中

​ 4. 效果演示

​ 发现其中mainConfig也是一个组件,是因为@Configuration也是一个@Component

​ 5. excludeFilters,过滤不扫描的内容。

它是一个Filter()数组

//excludeFilters = Filter[] 指定扫描的时候按照规则排除哪些规则
//includeFilters = Filter[] 指定扫描的时候只需要包含哪些组件
//useDefaultFilters 默认为true,加载所有组件

​ 6.@ComponentScan

​ 在8几以上中才可以

如果不是,就使用@ComponentScan,指定扫描策略

FilterType.ASSIGNABLE_TYPE 按照给定的类型

FilterType.ASPECTJ 使用ASPECTJ表达式(不太常用)

FilterType.REGEX 使用正则表达式

实现TypeFilter

top.p3wj中的每一个类都会进入进行匹配

1.4-组件注册 @Scope

默认单实例

* ConfigurableBeanFactory#SCOPE_PROTOTYPE  prototype   多实例
* ConfigurableBeanFactory#SCOPE_SINGLETON  singleton   单实例(默认值)
* org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST  request 同一次请求创建一个实例
* org.springframework.web.context.WebApplicationContext#SCOPE_SESSION  session 同一个session创建一个实例

改为多实例后

这其实就相当于在xml文件中,Bean里加上scope

ioc容器启动会创建对象,放到ioc容器中,以后每次获取就是直接从容器(map.get())中拿

1.下面演示单实例

把test02中下面的注释掉

2.多实例情况

就不打印“给容器中添加Person…”了

ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象。

1.5-组件注册@Lazy-bean懒加载

  • 单实例bean,默认在容器启动的时候创建对象
  • 懒加载:容器启动不创建对象,第一次使用(获取)Bean创建对象,并初始化

即在第一次获得的时候才加载

若取消懒加载

1.6-组件注册 @Conditional 按照条件给容器注入Bean

@Conditional ,按照一定的条件进行判断,满足条件给容器中注册Bean

先前准备:

要求:

* 如果是MacOs,给容器注册 jobs
* 如果是linux,给容器注册linus

通过applicationContext拿到一个运行的环境

要传入一个Condition数组。@Conditional({})

配置两个实现了Condition的类

设置一下参数

//boolean pserson = registry.containsBeanDefinition("pserson");//也可判断容器中是否包含一个Bean。也可给容器中注册Bean

可以做非常多的判断条件

也可放在类上,含义即满足当前条件,这个类中配置的 所有bean注册才能生效

注意

若有多个,则为按照顺序判断(猜测)已经设置-Dos.name=Linux

1.7-组件注册 @Import快速导入

/**
 * 给容器中注册组件:
 * 1) 包扫描+组件标注注解 (@Controller/@Service/@Repository/@Component)[自己写的]
 * 2) @Bean[导入的第三方包里面的组件],但是它比较麻烦(需要return等)
 * 3) @Import[快速给容器导入一个组件]
 */

1.新建一个color类

2.使用@Import

3.测试

//导入组件,id默认是组件的全类名,@Import(要导入到容器中到组件),容器中就会自动注册这个组件,id默认是全类名

可以导入多个,现写一个Red类

1.7.1-组件注册 @ImportSelector

2)  ImportSelector:返回需要导入的组件的全数组
public interface ImportSelector {

   /**
    * Select and return the names of which class(es) should be imported based on
    * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
    */
   String[] selectImports(AnnotationMetadata importingClassMetadata);

}

前提条件:

打上断点进行调试

结果:

如果返回null:---->return null;会报空指针,因为在拿类名的时候

所以不要返回null,可以返回一个空数组

return new String[]{};

可获取到所以注解信息及类相关的

因为是return的,所以也被导入了

1.7.2-组件注册 @ImportBeanDefinationRegister

ImportBeanDefinitionRegistrar 手动注册Bean

command+alt+b查看实现类

注意,import方式注入的名称为全类名

1.8-组件注册 @FactoryBean

通过此方法把对象放到容器中

结果:

//工厂获取的是调用getObject创建的对象
@Override
public boolean isSingleton() {
    return false;
}

isSingleton是false情况下是多实例,每一次获取都调用getObject

Reason:

使用Spring提供的FactoryBean(工厂Bean)
*              1)  默认获取到到是工厂bean调用getObject创建的对象
*              2)  要获取工厂Bean本身,我们需要给id前面加一个&
*                  &colorFactoryBean

二、生命周期

2.1-生命周期 @Bean指定初始化和销毁方法

在以前,可以指定初始化和销毁方法

创建Car

public class MainConfigOfLifeCycle {
    @Bean
    public Car car(){
        return new Car();
    }
}

以上针对单实例对象

通过调用close()关闭

2.当改为多实例Bean当时候

​ 2.1 当获取的时候才会初始化

​ 2.2 容器关闭后不会进行销毁

* bean的生命周期:
*      bean创建---初始化---销毁的过程
* 容器管理bean的生命周期:
* 我们可以自定义初始化和销毁方法;容器在  bean进行到当前生命周期的时候调用我们自定义的初始化和销毁方式
*构造(对象创建)
*      单实例:在容器启动的时候创建对象
*      多实例:在每次获取的时候创建对象
*初始化:
*      对象创建完成,并赋值好,调用初始化方法。。。
*销毁:
*      单实例:容器关闭的时候
*      多实例:容器不会管理这个bean,容器不会调用销毁方法;需要手动调用
* 1)、指定初始化和销毁方法:
*          指定init-method和destroy-method方法

2.2-生命周期 InitializingBean和DisposableBean

在这里提出一个问题,@Bean不搭配@Configuration使用跟搭配有什么区别(还未解决)

通过包扫描的方式进行注册,同时通过实现接口进行初始化和销毁

 2)、通过让Bean实现InitializingBean(定义初始化逻辑)

2.3-生命周期 @PostConstruct和@PreDestroy

这是java规范的注解,目前java8能用

* 3)、可以使用JSR250:
*          @PostConstruct:在bean创建完成并且属性赋值完成:来执行初始化方法
*          @PreDestroy:在容器销毁bean之前通知我们进行清理工作

2.4-生命周期 BeanPostProcessor(后置处理器)

先创建对象-》〉》〉初始化

* 4)、BeanPostProcessor【interface,bean的后置处理器:
*          在bean初始化前后进行一些处理工作:
*          postProcessBeforeInitialization:在初始化之前工作
*          postProcessAfterInitialization:在初始化之后工作

2.4.1-生命周期 BeanPostProcessor原理

打断点debug一下

1.查看调用方法栈,往上依次看

10.来看怎么创建的

12.创建好后准备初始化

13.原理体现的地方

点进去看一下,里面的内容

applyBeanPostProcessorsAfterInitialization 就不看了,类似的
*          遍历得到容器中所有的BeanPostProcessor:挨个执行beforeInitialization
*          一单返回null,跳出for循环,不会执行后面单BeanPostProcessor
*          populateBean(beanName, mbd, instanceWrapper); 给bean进行属性赋值的
*
*       initializeBean:
*      {
*          wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
*          this.invokeInitMethods(beanName, wrappedBean, mbd);
*          wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
*      }

2.4.2-生命周期 spring底层对BeanPostProcessor的使用

我们来看一下ApplicationContextAwareProcessor,实现的BeanPostProcessor

其实看看之前写的MyBeanPostProcessor,也是实现了它的方法

ApplicationContextAwareProcessor是封装好了这些实现

再来看下ApplicationContextAware

如果是就调用下面的 invokeAwareInterfaces(bean);

点进去查看

来debug看一下

准备如下:

Debug:

并且会把ioc容器传进来,怎么传进来呢?接下来根据方法栈来看下之前调用的

1.在这里调用 postProcessBeforeInitialization

再来看看 BeanValidationPostProcessor

该PostProcessor是用来做数据校验的,在web用的比较多

再来看看 InitDestroyAnnotationBeanPostProcessor

为什么Dog中标注了这样的注解它就知道在哪执行呢?

我们打一个断点来看一下

**出了个问题,init不执行(以后解决)**发现的问题为:
我还重写了个BeanPostProcessor的postProcessBeforeInitialization方法,@PostConstruct也是用

这里重写了:

再来看看 AutowiredAnnotationBeanPostProcessor

@Autowired也是通过这个来注值的

* spring底层对BeanPostProcessor的使用
*          bean赋值、注入其他组件,@Autowired,生命周期注解功能,@Async,等等
*          都是用BeanPostProcessor来完成的

三、属性赋值

3.1-属性赋值 @Value

并没有赋值,在以前的beans.xml文件中:

是通过这样的方式

我们有一个对应的@Value

如果要在beans.xml中使用${}取properties中的值就要配上这个名称空间

并采用以下方式:

ApplicationContext applicationContext2 = new ClassPathXmlApplicationContext("beans.xml");

但是启动会报错:

这是因为少了context相关的解析文件。

解决如下,在 xsi:schemaLocation 中添加:

3.2-属性赋值 @PropertySource加载外部配置文件

@PropertySource() 	属性的来源

因为是运行时候的变量,所以还可以用applicationContext.getEnvironment

也可用PropertySources,是一个可重复标注的注解

四、自动装配

4.1-自动装配 @Autowired & @Qualifier & @Primary

此外注意,在此版本中:

准备:

两个Dao,通过labe的设置看注入的是哪一个。相同类型,一个叫bookDao,一个叫bookDao2

你可以看见第二个报错了,因为按照BookDao.class去找的

@Autowired 如果找到相同类型组件,就需要按照属性名去寻找

如:BookDao bookDao;就是按照bookDao去寻找

我现在按照名字去找,可以发现这两个BookDao是不一样的,即@Repository与@Bean返回 注入的两者不一样

虽然在BookService通过@Autowired默认方法吗作为id注入,但是我们可以通过@Qualifier去改变

另外:

将BookDao的@Repository注释掉

此时相当于容器中没有任何一个BookDao

运行时会报错

看一下@Autowired,我们要达到没有该Bean就不注入的效果

此时service就正常了

我们发现如果容器同一个类型要用多个就要写多次@Qualifier,那么可以选用@Primary,即让spring进行自动装配的时候,默认使用首选的bean

总结

4.2-自动装配 JSR250-@Resource、JSR330-@Inject

@Resource是默认按照属性的名称

@Inject需要导入maven依赖

4.3-自动装配 方法、构造器位置的自动装配 & Aware注入Spring底层组件 & 原理

@Autowired:

1、标注在方法位置

给Car也加上@Component,通过配置类@ComponentScan扫描进去

默认加载ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作

加到参数上,效果也一样

如果只有一个有参构造器,@Autowired可以不用写

以@Bean的方式注入

准备

自定义

传进来这个applicationContext我们就能用,类似这样的有很多

总接口是Aware

找几个Aware来看一下

解析字符串的值

这些Aware都是由相应的XXXAwareProcessor来处理的

我们来看一下怎么将applicationContext注入进来的

打一个断点:

跟之前是类似的

总结一下

4.4-自动装配 @Profile 根据环境注册Bean

引入c3p0和mysql-connector

配置dbconfig.properties

并加载@PropertySource(“classpath:/dbconfig.properties”)

来自spring的黑科技

另一种方式,Aware接口

那么来看看@Profile

* @description Profile:
*                  Spring为我们提供的可以根据当前环境,动态地激活和切换一些列组件的功能:
* 开发环境、测试环境、生产环境:
* 数据源:(/A)(/B)(/C)
* @Profile 指定组件在哪个环境下才能被注册到容器中。不指定任何环境下都能注册这个组件
* 1)、加了环境表示的bean,只有这个环境被激活的时候才能被注册到容器中,默认是default环境

默认是"default",可以看见只有标了"default"才会被加入到容器中

那么怎么切换环境呢?

最简单的方法,使用命令行参数

2.代码的方式

针对于AnnotationConfigApplicationContext

配置类一注册进来,容器就启动刷新了,环境还没有设置好

如果写在类上,就代表整个类里面的内容是否会被加载

* @Profile 指定组件在哪个环境下才能被注册到容器中。不指定任何环境下都能注册这个组件
* 1)、加了环境表示的bean,只有这个环境被激活的时候才能被注册到容器中,默认是default环境
* 2) 、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
* 3)、没有标注环境标识的bean,任何环境下都会加载

转载:https://blog.csdn.net/qq_29310729/article/details/106376587
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场