IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring IOC注解笔记(杂) -> 正文阅读

[Java知识库]Spring IOC注解笔记(杂)

写在前面

本文是笔者自己在学习SpringIOC注解时的随堂笔记,仅作备份查阅用,不具备可阅读学习性(跑


一、组件注册

1.1 @Configuration

在没配置@ComponentScan的情况下,默认只扫描和主类处于同包下的Class。

平时创建bean用的配置方法:beans.xml

<bean  id="xml_user" class="com.xlaoer.test.pojo.User" scope="prototype">
    <constructor-arg name="username" value="mccbyxml"></constructor-arg>
    <constructor-arg name="password" value="123"></constructor-arg>
</bean>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

现在创建一个配置类:@Configuration 用@bean来创建bean

@Configuration
public class AnnotationConfig {
    @Bean("anno_user")
    public User getUser(){
        return new User("mcc","123456");
    }
}
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationConfig.class)

1.2 @ComponentScan里的Filter来条件扫包

@AliasFor 是为了属性起别名

为什么要给 value 属性和 path 属相互相设置别名也是有原因的。我们知道在 Spring 中给 value 属性设置值是可以省略属性的,比如可以写成:@RequestMapping("/foo")

这样写比较简洁,但是这样可读性不高,我们并不知道 value 属性代表什么意思。如果给这个属相设置一个 path 别名的话我们就知道这个是在设置路径。

但是要注意一点,@AliasFor 标签有一些使用限制:

  • 互为别名的属性属性值类型,默认值,都是相同的;
  • 互为别名的注解必须成对出现,比如 value 属性添加了@AliasFor(“path”),那么 path 属性就必须添加@AliasFor(“value”);
  • 另外还有一点,互为别名的属性必须定义默认值。

FilterType各自种类。FilterType.ANNOTATION标志按照注解进行包含排除,后面传需要排除或包含的注解类,如:Controller.class

ASSIGNABLE_TYPE:按照给定类型 classes={BookService.class}

REGEX:使用正则表达式

ASPECTJ:

CUSTOM:自定义过滤规则:需要一个实现TypeFilter接口的类,接口定义的方法里的参数可以获取到一些信息。

? 自定义Filter 获取注解、类路径等信息
在这里插入图片描述

1.3 @Scope作用域

默认单实例(singleton)

singleton、prototype、request、session

request:同一次请求创建一个实例

session:同一个session创建一个实例

1.4 @Lazy懒加载

懒加载:容器启动不创建对象,第一次调用(获取)Bean的时候再创建对象

懒加载是一种取巧办法,容器调用getBeanDefinitionNames方法或者getBean等要用到Bean的的时候,才会创建对象

1.5 @Conditional 条件注册Bean

@Conditional 可以放在类或方法上 放在方法上 单个bean 类上就是整个类的bean是否注册

@Contional注解 条件注册bean 传入一个实现了Conditional接口的类

其方法里的参数:ConditionContext context可以获取到beanFactory、Classloader、environment、registry等

然后可以根据不同的环境来判断这个bean是否需要注册。

String os = environment.getProperty("os.name"); //获取当前操作系统的型号

在VM Options,可以修改os.name的值 -Dos.name=linux

1.6 @Import导入Bean

  1. value直接放类.class,导入的组件的bean的id默认是bean的全类名

  2. ImportSelector接口实现类.class(自定义逻辑)(实现类方法的返回值就是要注册到容器中的类的全类名)

    ? SpringBoot用第二种:ImportSelector比较多

  3. ImportBeanDefinitionRegistrar:手动注册bean到容器中(可以指定bean名,不需要为全类名)(如下图)

在这里插入图片描述

1.7 FactoryBean组件注册Bean(工厂Bean)

实现FactoryBean接口,泛型为你要的bean。注意这里是FactoryBean不是BeanFactory

FactoryBean的特殊之处在于它可以向容器中注册两个Bean,一个是它本身,一个是FactoryBean.getObject()方法返回值所代表的Bean。

在我们通过FactoryBean的实现类获得具体bean的时候,会调用getObject方法来返回其创建的泛型对象。

所以如果我们需要FactoryBean实现类的bean,就需要,加一个&

//com.xlaoer.test.factory.FactoryBeanTest@3ec300f1
System.out.println(applicationContext.getBean("&com.xlaoer.test.factory.FactoryBeanTest"));
//com.xlaoer.test.controller.TestImport@482cd91f (FactoryBean的getObject方法创建的对象)
System.out.println(applicationContext.getBean("com.xlaoer.test.factory.FactoryBeanTest"));

还有一个isSingleton来让getObject返回的是否单例对象。

Spring 有一个factorybean 与其他框架联动用的很多 工厂模式

Spring使用工厂模式可以通过 BeanFactoryApplicationContext 创建 bean 对象。

摘自Spring都用到了哪些设计模式?


二、生命周期

Bean销毁:

? 如果Bean是单实例:容器关闭的时候

? 如果Bean是多实例:容器不会管理这个Bean,不会调用销毁方法(GC来?试了一下System.gc()好像也不行),不过会调用Bean的init方法(如果有)

所以:Spring IOC容器中只存放单例Bean

https://developer.51cto.com/art/202104/660312.htm

2.1 @Bean注解指定初始化和销毁方法

Bean注解内的方法

String initMethod() default "";

String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;

通过在Bean注解里标注User里的方法(initUser、destoryUser)

@Bean(initMethod = "initUser",destroyMethod = "destoryUser")
public User beanAnnowithInit(){
    return new User("beanAnnowithInit","23");
}

2.2 实现InitializingBean和DisposableBean接口

Spring会自动识别类是否实现了这两个接口,实现了就调用他们的方法。

InitializingBean接口的方法:

@Override
public void afterPropertiesSet() throws Exception {...}

DisposableBean接口的方法:

@Override
public void destroy() throws Exception {...}

2.3 @PostConstruct和@PreDestroy注解 (JSR250

Spring会自动识别类中的方法是否标注了这两个注解,标注了就调用他们的方法。

@PostConstruct
public void initUserWithAnno(){
    System.out.println("User's init method");
}
@PreDestroy
public void destoryUserWithAnno(){
    System.out.println("User's destory method");
}

2.4 【重点】BeanPostProcessor接口

可以创建一个自定义类实现BeanPostProcessor接口,如MyBeanPostProcessor

注意:这个自定义类需要注册到IOC容器中(可以@Component标记类)

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

postProcessBeforeInitialization:在Bean 初始化(init)之前工作

postProcessAfterInitialization:在Bean 初始化(init)之后工作

Spring中实现了BeanPostProcessor 接口的一些接口:

ApplicationContextAware接口:传入ioc容器——底层:ApplicationContextAwareProcessor类会在bean init前判断Bean是否实现了*.Aware接口,如果有,就调用一些方法传入东西

BeanValidationPostProcessor类:实现了BeanPostProcessor 接口的预设类,实现了postProcessBeforeInitialization和postProcessAfterInitialization方法,里面的doValidate(bean);会在init前后对数据进行校验

InitDestoryAnnotationBeanPostProcessor类:让@PostConstruct、@PreDestroy注解生效。找到标注了生命周期注解的方法并且利用反射invoke它们

Spring底层对BeanPostProcessor的使用:

? bean赋值,注入到其他组件,@Autowired,生命周期注解功能,@Async,xxx都是用BeanPostProcessor来完成的

在这里插入图片描述

这是自己搞得 initUserWithAnno方法是实现init方法的其中一种(本文2.3) 同理destory

下图主要来理解不同方法的调用时期(BeanPostProcessor接口提供围绕每个bean的init调用的前后钩子函数)

在这里插入图片描述

BeanPostProcessor源码

populateBean是为属性赋值

在这里插入图片描述

applyBeanPostProcessorsBeforeInitialization方法:

在这里插入图片描述


三、属性赋值

3.1 @Value赋值

以前用配置文件为bean赋值的方式:

<bean id="user" class="pojo.User" name="u1 u2,u3;u4">
    <property name="name" value="chen"/>
</bean>

因为IOC容器只管理单例bean,所以直接在实体类中对着实体类@Value()进行赋值

注解里可以填写的内容:

  1. 基本数值

  2. spEL (spring表达式)比如#{} 计算 ${}取出环境变量中的值(如配置文件)

@Repository
public class Car {
    @Value("特斯拉")
    private String name;
    @Value("#{10+20}")
    private Integer cost;
    ...
}

3.2 @PropertySource加载外部配置文件

以前xml配置的时候

<context:property-placeholder location="classpath:person.properties"/>
<bean id="person" class="pojo.Person" scopr="prototype">
    <property name="age" value="${age}"/>
</bean>

现在直接用@PropertySource注解:(哪个实体类要用配置文件,可以直接加在实体类上,或者直接在配置类文件上加注解)

@Repository
public class Seat {
    @Value("${seatname}")
    private String seatname;
    ...
}

或者可以在配置类上加,配合ComponentScan扫注解的时候环境里一样有配置加载进来

@Configuration
@PropertySource(value = {"classpath:settings.properties"})
@ComponentScan("com.xlaoer.test2")
public class AnnotationConfig2 {...}

applicationcontext.getEnvironment()的getProperty方法

System.out.println("seatname:"+applicationContext.getEnvironment().getProperty("seatname")); 

四、自动装配

自动装配:Spring利用依赖注入(DI),完成对IOC容器中各个组件依赖关系的赋值

4.1 @Autowired&@Qualifier&@Primary

@Autowired:默认是byType方式,如果匹配不上,就会byName

如果byName找不到就会报错:解决:配合@Qualifer(“指定具体name”)

@Qualifier("seat2")
@Autowired
private Seat carseat;

**@Autowired默认容器中有这个bean ,没有就会报错;**这个注解有required = flase 就不需要强制要求这个bean了

如果有bean被@Primary修饰,而且@Autowired没有配合@Qualifier明确指定,那么同类型下被@Primary修饰的bean会被赋值

如果既有@Primary,又有@qualifer,那么会去装配@Qualifer指定的那个bean

4.2 @Resource

@Resource:默认是byName方式,如果匹配不上,就会byType

相比@Autowired缺点:没有支持@Primary功能和required属性

@Resource(name = "seat2")
private Seat carseat;

@Autowired:Spring定义的;@Resource、@Inject都是java规范

AutowiredAnnotationBeanPostProcessor:解析完成自动装配功能的PostProcessor

4.3 方法、构造器位置的@Autowired

@Autowired的Target有构造器、参数、方法、属性

标在方法上:

标在构造器上(如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取

在这里插入图片描述

@Bean标注的方法创建对象的时候,方法参数的值从容器中获取,可以省略@Autowired

在这里插入图片描述

@Autowired不管标注在哪儿,都是从容器中获取bean

不用记这么细,反正到时候根据报错提示来复习

4.4 【重点】 Aware注入Spring底层组件&原理

Aware接口:用于想使用spring底层对象的接口

可以创建一个类实现ApplicationContextAware、BeanNameAware、EmbeddedValueResolverAware(解析String中的#{} ${})

xxxAware接口功能:使用xxxProcessor传入 【重点】

如:ApplicationContextAware ==> ApplicationContextAwareProcessor

populateBean ->initializeBean

? ->applyBeanPostProcessorsBeforeInitialization ->invokeInitMethods ->applyBeanPostProcessorsAfterInitialization

? ->getBeanPostProcessors().postProcessBeforeInitialization

? ->invokeAwareInterfaces

4.5 @Profile环境搭建(略)

@Profile

Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能

开发环境、测试环境、生产环境

比如根据数据源来切换(这里以c3p0为例)

@PropertySource 来导入数据库的配置文件

多种注入方式体验1.@Value( k e y ) 2. 实 现 E m b e d d e d V a l u e R e s o l v e r A w a r e 接 口 用 V a l u e R e s o l v e r 来 r e s o l v e S t r i n g V a l u e ( {key}) 2.实现EmbeddedValueResolverAware接口用ValueResolver来resolveStringValue( key)2.EmbeddedValueResolverAwareValueResolverresolveStringValue({key})

4.6 配合@Profile,根据环境注册bean

@Profile(“xxxxx”) xxxx是指自定的环境名称标识 ,可以作用在类上,也可以作用在方法上

加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中 默认激活@Profile(“default”)

没有标注的bean,在任何环境下都会被加载

激活环境方法:

  1. 命令行动态参数:VM arguments -> -Dspring.profiles.active=xxxx

  2. 使用无参的ApplicationContext构造器(是不是因为有参的没有profile的相关参数?),然后getEnvironment().setActiveProfiles(“xxx”),最后再register(配置类.class),最后的最后再调用refresh方法(这些其实就是有参里面的代码,不过我们手动调用了)

    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.getEnvironment().setActiveProfiles("Windows");
    applicationContext.register(AnnotationConfig2.class);
    applicationContext.refresh();
    

附:有参的ApplicationContext代码(2.就是参照了这个手动调用的)

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

toString使用错误导致OOM

我改写toString 上面return this 想打印@来判断是否同一个bean,结果造成了oom

原因:toString方法中打印的其他对象的toString会间接循环,导致你的方法一直在调用方法,且没有返回,每次调用导致栈上升,最后导致栈溢出,所以在使用toString方法时需要注意到递归问题,不要造成了死递归。

在这里插入图片描述

后面解决,把this改成super.toString()就好了

其实本质是 return getClass().getName() + "@" + Integer.toHexString(hashCode());

ioc还有一个三级缓存解决循环依赖的问题没讲

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-17 12:37:37  更:2021-11-17 12:39:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:51:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码