spring5注解开发学习笔记

 

目录

 

前言

首先本篇文章是基于尚硅谷雷神讲解的spring注解驱动开发视频。
注意:视频中使用的是spring4版本,目前使用spring5版本,略有差异。
本篇文章将会带你进入spring注解驱动开发的学习大门中。
文章将会按照下图的脉络进行学习。

特别注意:源码讲解部分因为本人无任何工作经验, 学习时间过短,理解不了。这部分等工作之后再来看,未完待续。
在这里插入图片描述
容器
容器作为第一大部分,内容包括:

  1. AnnotationConfigApplicationContext
  2. 组件添加
  3. 组件赋值
  4. 组件注入
  5. AOP
  6. 声明式事务

扩展原理
扩展原理作为第二大部分,内容包括:

  1. BeanFactoryPostProcessor
  2. BeanDefinitionRegistryPostProcessor
  3. ApplicationListener
  4. Spring容器创建过程
    这部分主要是源码讲解。

Web
Web作为第三大部分,内容包括:

  1. servlet3.0
  2. 异步请求

这部分,其实就是SpringMVC,在这个部分中,我们会重点来说异步请求。


1 @Configuration和@Bean(给容器中注册组件)

1.1 准备工作 — 创建工程

使用idea21.3创建一个maven工程(spring-annotion)。

1.2 配置依赖 — pom.xml

将打包方式设置为jar包。引入spring-context依赖,这里我们选择最新的5.3.22版本作为配置工具。

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.atguigu</groupId> <artifactId>spring-annotion</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.22</version> </dependency> </dependencies> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project>

1.3 创建实体类 — Person

package com.atguigu.bean; /** * @author hxld * @create 2022-08-03 22:34 */ public class Person { private String name; private Integer age; public Person() { } public Person(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }

1.4 使用xml配置方式

1.4.1 创建一个beans.xml

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="person" class="com.atguigu.bean.Person"> <property name="age" value="18"></property> <property name="name" value="zhangsan"></property> </bean> </beans>

1.4.2 创建测试类 — MainTest

public class MainTest { public static void main(String[] args) { //beans.xml配置方式的测试 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); //本来是object,然后强转为person Person bean = (Person) applicationContext.getBean("person"); System.out.println(bean); } }

1.5 使用注解方式

使用上面的xml文件配置方式,我们发现太过于繁琐。那不如使用注解方式来实现。

1.5.1 创建配置类 — MainConfig

配置类 MainConfig其实就相当于配置文件beans.xml

@Configuration : 告诉spring这是一个配置类。
@Bean :给容器中注册一个bean。相当于beans.xml中的bean。beans.xml中的id对应配置类中的方法名,beans.xml中的class对应的是配置类中的返回值类型。

//配置类 == 配置文件(beans.xml) //告诉spring这是一个配置类 @Configuration public class MainConfig { //给容器中注册一个bean ,对应beans.xml中的bean。类型:返回值类型,id:方法名 //value可以写person //@Bean("person01") @Bean("person") public Person person() { return new Person("lisi",20); } }

启动时调用方法,将方法的返回值放到IOC容器中,方法名作为组件的id。

1.5.2 测试

public class MainTest { public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); Person bean = applicationContext.getBean(Person.class); System.out.println(bean); //获取Person这个类型的组件在IOC容器中的名字 String[] type = applicationContext.getBeanNamesForType(Person.class); for (String name: type) { System.out.println(name); } } }

在这里插入图片描述

上面我们发现是person,这不就是我们创建的配置类的方法名吗。

经观察,使用注解注入JavaBean时,bean在IOC容器中的名称就是使用@Bean注解标注的方法名称。我们可不可以为bean单独指定名称呢?那必须可以啊!只要在@Bean注解中明确指定名称就可以了。如果我们将person这个方法名改为person01,那么测试类中测试组件在IOC容器中的名字时输出就是person01。都不需要修改方法名,只需要在@Bean(“person01”)既可。
在这里插入图片描述
在这里插入图片描述
输出结:person01

1.6 小结

我们在使用注解方式向Spring的IOC容器中注入JavaBean时,如果没有在@Bean注解中明确指定bean的名称,那么就会使用当前方法的名称来作为bean的名称;如果在@Bean注解中明确指定了bean的名称,那么就会使用@Bean注解中指定的名称来作为bean的名称。

2 @ComponentScan( 包扫描)

在实际项目中,我们更多的是使用Spring的包扫描功能对项目中的包进行扫描,凡是在指定的包或其子包中的类上标注了@Repository、@Service、@Controller、@Component注解的类都会被扫描到,并将这个类注入到Spring容器中。

Spring包扫描功能可以使用XML配置文件进行配置,也可以直接使用@ComponentScan注解进行设置,使用@ComponentScan注解进行设置比使用XML配置文件来配置要简单的多。

2.1 创建三层架构组件

BookController

@Controller public class BookController { }

BookDao

@Repository public class BookDao { }

BookService

@Service public class BookService { }

2.2 使用xml配置方式

2.2.1 在beans.xml中配置

使用xml配置方式进行包扫描需要引入名称空间context
在这里插入图片描述
具体配置:

<!--包扫描,只要标注了@Controller @Service @Repository @Component的都会被加入到ioc容器中--> <context:component-scan base-package="com.atguigu"></context:component-scan> <!--注意包扫描还有一个默认规则,是扫描全部,即use-default-filters = true,我们设置只包含/只扫描的时候要禁用,设置为false--> <!--<context:component-scan base-package="com.atguigu" use-default-filters="false"></context:component-scan>-->
  1. 包扫描,只要标注了@Controller @Service @Repository @Component的都会被加入到ioc容器中。

  2. 注意包扫描还有一个默认规则,是扫描全部,即use-default-filters = true,我们设置只包含/只扫描的时候要禁用,设置为false。

2.2.2 测试

public class IocTest { @Test public void test01(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); //查看bean容器中所有的组件,输出名字 String[] names = applicationContext.getBeanDefinitionNames(); for (String name: names) { System.out.println(name); } } }

在这里插入图片描述

问题:不知道为什么包扫描使用xml配置文件扫描不出自己创建的三层架构组件?

2.3 @ComponentScan(包扫描)

2.3.1 配置中上进行注解

在这里插入图片描述
在这里插入图片描述

@ComponentScan(value="com.atguigu") //设置哪些不扫描 excludeFilters = Filter[] 指定扫描的时候按照什么规则排除哪些组件,不包含哪些组件 //type是指按哪种类型来进行过滤,classes为一个数组,里面为具体的过滤条件实体。 @ComponentScan(value="com.atguigu",excludeFilters = { @ComponentScan.Filter(type= FilterType.ANNOTATION,classes = {Service.class}) }) //includeFilter =Filter[] 只包含哪些组件,必须设置useDefaultFilters = false,禁用默认全局扫描 @ComponentScan(value="com.atguigu",includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}) },useDefaultFilters = false ) //设置多个扫描策略 @ComponentScans( value={ @ComponentScan(value="com.atguigu",includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}) },useDefaultFilters = false ) ,@ComponentScan(value="com.atguigu",excludeFilters = { @ComponentScan.Filter(type= FilterType.ANNOTATION,classes = {Service.class}) })} )
  1. excludeFilters = Filter[]设置哪些不扫描 指定扫描的时候按照什么规则排除哪些组件,不包含哪些组件,
    type是指按哪种类型来进行过滤,classes为一个数组,里面为具体的过滤条件实体。
  2. includeFilter =Filter[]只包含哪些组件,必须设置useDefaultFilters = false,禁用默认全局扫描。
  3. @ComponentScans:可设置多个扫描策略。

2.3.2 测试

public class IocTest { @Test public void test01(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); //查看bean容器中所有的组件,输出名字 String[] names = applicationContext.getBeanDefinitionNames(); for (String name: names) { System.out.println(name); } } }

在这里插入图片描述

2.3.3 @ComponentScan本质

@ComponentScan注解就是一个重复注解。
在这里插入图片描述

在这里插入图片描述

3 使用TypeFilter指定@ComponentScan注解的过滤规则

3.1 FilterType的几种类型

FilterType

  • ANNOTATION :按照注解
  • ASSIGNABLE_TYPE :按照指定类型 ,比如说classes={BookService.class}
  • ASPECTJ:基本不用
  • REGEX:按照正则表达式
  • CUSTOM:按照自定义规则。需要我们创建一个 TypeFilter的实现类

3.2 使用自定义过滤规则

3.2.1 创建一个TypeFilter的实现类 — MyTypeFilter

继承TypeFilter

// FilterType 的自定义规则 public class MyTypeFilter implements TypeFilter { //match方法返回值是布尔类型,如果是true--匹配成功,false--匹配失败 //metadataReader ----读取到的当前正在扫描的类的信息 //metadataReaderFactory -----可以获取到其他任何类信息的 @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类的信息(接口、类型等) ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类资源(类的路径) Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); System.out.println("------->" + className); //自定义过滤规则,service,controller中就包含er if(className.contains("er")){ return true; } //false表示一个都不匹配,即一个都不扫描 return false; } }
  • match方法返回值是布尔类型,如果是true–匹配成功,false–匹配失败
  • metadataReader ----读取到的当前正在扫描的类的信息
  • metadataReaderFactory -----可以获取到其他任何类信息的

3.2.2 配置类中配置

// FilterType 的自定义规则 @ComponentScan(value="com.atguigu",includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class}) },useDefaultFilters = false )

3.2.3 测试

public class IocTest { @Test public void test01(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); //查看bean容器中所有的组件,输出名字 String[] names = applicationContext.getBeanDefinitionNames(); for (String name: names) { System.out.println(name); } } }

在这里插入图片描述

4 @Scope(设置作用域)

4.1 注解方式

4.1.1 创建MainConfig2配置类

@Configuration public class MainConfig2 { //prototype:多实例 ioc容器启动并不会去调用方法创建对象放到容器中,每次获取的时候才会调用方法创建对象,获取一次创建一次。 //singleton:单实例(默认) ioc容器启动会调用方法创建对象放到Ioc容器中,以后每次获取就是直接从容器(相当于map.get())中拿。 //request:同一次请求创建一个实例 --- web工程中 -----基本不使用 //session:同一个session创建一个实例 --- web工程中 -----基本不使用 @Scope("prototype") //调整作用域 //默认是单实例对象 @Bean("person") public Person person(){ //测试实例在多久创建,如下语句。 System.out.println("给容器中添加person...."); return new Person("张三",25); } }

scopr的四种类型:

  1. prototype:
  • 多实例
  • ioc容器启动并不会去调用方法创建对象放到容器中,每次获取的时候才会调用方法创建对象,获取一次创建一次。
  1. singleton
  • 单实例(默认)
  • ioc容器启动会调用方法创建对象放到Ioc容器中,以后每次获取就是直接从容器(相当于map.get())中拿。
  1. request:
  • 同一次请求创建一个实例 — web工程中 -----基本不使用
  1. session
  • 同一个session创建一个实例 — web工程中 -----基本不使用

4.1.2 测试

当我们设置多实例的时候:

@Test public void test02() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); //查看bean容器中所有的组件,输出名字 String[] names = applicationContext.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); //ioc容器创建完成(测试多实例对象在何时被创建) System.out.println("ioc容器创建完成......"); //测试@bean给我们创建的是单实例对象 //第一次获取 Object bean = applicationContext.getBean("person"); //第二次获取 Object bean2 = applicationContext.getBean("person"); //判断这两个对象是否相等 System.out.println(bean == bean2); //结果为true,即默认是单实例的 //当我们设置@Scope("prototype")时,结果为false,即不是同一个对象,多实例 } }

输出结果:
在这里插入图片描述
结果为false,表示每次创建的实例都不是同一个。

4.1.2 beans.xml中配置

在beans.xml中我们这样设置。如下图:

在这里插入图片描述

5 @Lazy — bean懒加载

Spring在启动时,默认会将单实例bean进行实例化,并加载到Spring容器中去。也就是说,单实例bean默认是在Spring容器启动的时候创建对象,并且还会将对象加载到Spring容器中。如果我们需要对某个bean进行延迟加载,那么该如何处理呢?此时,就需要使用到@Lazy注解了。

5.1 什么是懒加载

懒加载 :容器启动不创建对象,第一次使用(获取)Bean创建对象,并初始化。

5.2 注解实现方式

@Lazy // 懒加载 :容器启动不创建对象,第一次使用(获取)Bean创建对象,并初始化 //默认是单实例对象 @Bean("person") public Person person(){ //测试实例在多久创建,如下语句。 System.out.println("给容器中添加person...."); return new Person("张三",25); }

5.3 测试

在类中方法里面加入一句输出语句,代表实例被创建调用。如上。

[1] 使用懒加载

@Test public void test02() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); //查看bean容器中所有的组件,输出名字 String[] names = applicationContext.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } Object bean = applicationContext.getBean("person"); }

输出结果中没有调用类中的方法,意味着容器创建完成了,但是实例没有被创建。
在这里插入图片描述
当我们获取bean的时候,看测试结果
在这里插入图片描述
发现调用方法了,即创建实例。

[2] 未使用懒加载

不设置获取对象。
在这里插入图片描述
我们看到在容器创建的时候,就已经调用方法了。创建实例了。

6 @Conditional — 按照一定条件给容器中注入bean

6.1 概述

  • Spring支持按照条件向IOC容器中注册bean,满足条件的bean就会被注册到IOC容器中,不满足条件的bean就不会被注册到IOC容器中。

  • @Conditional注解可以按照一定的条件进行判断,满足条件向容器中注册bean,不满足条件就不向容器中注册bean。

  • @Conditional注解是由Spring Framework提供的一个注解,它位于 org.springframework.context.annotation包内。

在这里插入图片描述
从@Conditional注解的源码来看,@Conditional注解不仅可以添加到类上,也可以添加到方法上。在@Conditional注解中,还存在着一个Condition类型或者其子类型的Class对象数组,Condition是个啥呢?
在这里插入图片描述

可以看到,它是一个接口。所以,我们使用@Conditional注解时,需要写一个类来实现Spring提供的Condition接口,它会匹配@Conditional所符合的方法,然后我们就可以使用我们在@Conditional注解中定义的类来检查了。
在这里插入图片描述

6.2 创建接口实现类

@Conditional ({Condition}):按照一定的条件进行判断,满足条件给容器中注册Bean

  • 如果当前IOC容器操作环境是windows系统,则给容器中注册bill
  • 如果当前IOC容器操作环境是LINXU系统,则给容器中注册linus
//判断是否为Linux系统 public class LinuxCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //1.可以获取到IOC使用的beanfactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //2.获取类加载器 ClassLoader classLoader = context.getClassLoader(); //3. 获取当前环境信息 Environment environment = context.getEnvironment(); //4.获取到bean定义的注册类 BeanDefinitionRegistry registry = context.getRegistry(); //5.可以判断容器中的bean注册情况,也可以给容器中注册bean boolean person = registry.containsBeanDefinition("person"); String property = environment.getProperty("os.name"); if(property.contains("linux")){ return true; } return false; } }
//判断是否为Windows系统 public class WindowsCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String property = environment.getProperty("os.name"); if(property.contains("Windows")){ return true; } return false; } }

6.3 创建两个方法

@Bean("bill") public Person person01(){ return new Person("Bill Gates",62); }
@Bean("linus") public Person person02(){ return new Person("linus",48); }

6.4 在方法上使用注解

在这里插入图片描述

6.5 测试

[1] 使用注解

@Test public void test03() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); //获取当前操作环境 ConfigurableEnvironment environment = applicationContext.getEnvironment(); //动态获取环境变量的值 String property = environment.getProperty("os.name"); System.out.println(property); String[] namesForType = applicationContext.getBeanNamesForType(Person.class); for (String name : namesForType) { System.out.println(name); } //获取所有对象 Map<String, Person> personMap = applicationContext.getBeansOfType(Person.class); System.out.println(personMap); }

测试结果,当前操作环境是window 10,会创建bill这个bean。
在这里插入图片描述

[2] 不使用注解

在这里插入图片描述
bill 和 linus 都被创建了。

7 @Import — 给容器中快速导入一个组件

目前给容器中注册组件有以下几种方法。

  1. 包扫描+组件标注注解(@Controller/@Service/@Reposity/@Component ) ----局限于我们自己创建的类
  2. @Bean(导入的第三方包里面的组件)
  3. @Import(快速给容器中导入一个组件)

下面我们来使用@import注解给容器中快速导入一个组件。

7.1 创建两个类

public class Red { }
public class Color { }

7.2 使用注解快速给容器中导入组件

在这里插入图片描述
组件的id默认是组件的全类名。

7.3 测试

//抽取出一个通用的打印配置类中所有组件的方法 public void printBeans(AnnotationConfigApplicationContext applicationContext) { String[] definitionNames = applicationContext.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } } @Test public void testImport(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); printBeans(applicationContext); } }

在这里插入图片描述

8 在@Import注解中使用ImportSelector接口导入bean

ImportSelector:返回需要导入的组件的全类名数组;

8.1 创建MyImportSelector

//自定义逻辑返回需要导入的组件 public class MyImportSelector implements ImportSelector { //返回值,就是要导入到容器中的组件全类名 //AnnotationMetadata:当前标注@Import注解的类的所有注解信息 @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"com.atguigu.bean.Blue","com.atguigu.bean.Yellow"}; } }

使用selectImports方法自定义返回需要导入的组件。比如我们想要导入blue和yellow这两个。

8.2 创建两个类 — Blue Yellow

public class Blue { }
public class Yellow { }

8.3 测试

//抽取出一个通用的打印配置类中所有组件的方法 public void printBeans(AnnotationConfigApplicationContext applicationContext) { String[] definitionNames = applicationContext.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } } @Test public void testImport(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); printBeans(applicationContext); }

在这里插入图片描述
可以见到是输出了全类名并且创建了组件bean。

9 在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean

不仅可以使用@Import注解快速向容器中导入bean,也可以在@Import注解中使用ImportSelector接口的方法导入bean,今天,我们就来说说,如何在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean。

ImportBeanDefinitionRegistrar

package org.springframework.context.annotation; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.core.type.AnnotationMetadata; public interface ImportBeanDefinitionRegistrar { default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { this.registerBeanDefinitions(importingClassMetadata, registry); } default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }

ImportBeanDefinitionRegistrar本质上是一个接口。在ImportBeanDefinitionRegistrar接口中,有一个registerBeanDefinitions()方法,通过该方法,我们可以向Spring容器中注册bean实例。

Spring官方在动态注册bean时,大部分套路其实是使用ImportBeanDefinitionRegistrar接口。

所有实现了该接口的类都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其的bean初始化的,也能被aop、validator等机制处理。

9.1 创建接口实现类 — MyImportBeanDefinitionRegistrar

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /** AnnotationMetadata: 当前类的注解信息 BeanDefinitionRegistry:BeanDefinition注册类 把所有需要添加到容器中的bean调用:BeanDefinitionRegistry.registerBeanDefinition 手工注册进来。 */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){ //判断是否有下面两个组件 boolean definition = registry.containsBeanDefinition("com.atguigu.bean.Red"); boolean definition1 = registry.containsBeanDefinition("com.atguigu.bean.Blue"); //如果有 if(definition && definition1){ //指定bean的定义信息(Bean类型) RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); //进行注册bean名:rainBow registry.registerBeanDefinition("rainBow",beanDefinition); } } }

以上registerBeanDefinitions()方法的实现逻辑很简单,就是判断Spring容器中是否同时存在以com.atguigu.bean.Red命名的bean和以com.atguigu.bean.Blue命名的bean,如果真的同时存在,那么向Spring容器中注入一个以rainBow命名的bean。

9.2 创建RainBow

public class RainBow { }

9.3 测试

//抽取出一个通用的打印配置类中所有组件的方法 public void printBeans(AnnotationConfigApplicationContext applicationContext) { String[] definitionNames = applicationContext.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } } @Test public void testImport(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); printBeans(applicationContext); } }

在这里插入图片描述
测试见图。

9.4 @import注解小结

@Import(快速给容器中导入一个组件)

  • 1)@Import(要导入到容器中的组件):容器中就会自动注册这个组件,id默认是全类名。
  • 2)ImportSelector:返回需要导入的组件的全类名数组;
  • 3)MyImportBeanDefinitionRegistrar : 手动注册bean到容器中

10 使用FactoryBean向Spring容器中注册bean

10.1 创建一个spring自定义的FactoryBean

//创建一个Spring定义的FactoryBean public class ColorFactoryBean implements FactoryBean { //返回一个Color对象,这个对象会添加到容器中 @Override public Color getObject() throws Exception { System.out.println("ColorFactoryBean....getObject"); return new Color(); } //返回的类型 @Override public Class<?> getObjectType() { return Color.class; } //是单例? //true:这个bean 是单实例,在容器中保存一份 //false:多实例,每次获取都会创建一个新的bean; @Override public boolean isSingleton() { // return FactoryBean.super.isSingleton(); // return true; return false; } }
  • T getObject():返回由FactoryBean创建的bean实例,如果isSingleton()返回true,那么该实例会放到Spring容器中单实例缓存池中
  • boolean isSingleton():返回由FactoryBean创建的bean实例的作用域是singleton还是prototype
  • Class getObjectType():返回FactoryBean创建的bean实例的类型

这里,需要注意的是:当配置文件中标签的class属性配置的实现类是FactoryBean时,通过 getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。

10.2 创建一个color类

public class Color { }

10.3 在配置类中配置 — 注册组件

@Bean public ColorFactoryBean colorFactoryBean(){ return new ColorFactoryBean(); }

10.4 测试

[1] 输出对象

默认获取到的是工厂bean调用getobject创建的对象。设置单实例返回true:即单实例。
在这里插入图片描述

@Test public void testImport(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); printBeans(applicationContext); //工厂Bean获取的是调用getObject创建对象 //获取的类型:Color Object bean = applicationContext.getBean("colorFactoryBean"); //获取的类型:Color Object bean2 = applicationContext.getBean("colorFactoryBean"); System.out.println("bean的类型"+bean.getClass()); System.out.println(bean == bean2); }

测试结果:
在这里插入图片描述

创建一次,实例是单实例,返回的是getObject调用的对象。

[2] 返回工厂bean本身

@Test public void testImport(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); printBeans(applicationContext); //获取的类型:ColorFactoryBean 加一个前缀& 获取工厂Bean本身 Object bean3 = applicationContext.getBean("&colorFactoryBean"); System.out.println("bean3的类型"+bean3.getClass()); }

在这里插入图片描述

10.5 小结

使用Spring提供的FactoryBean(工厂Bean)

  • 1)默认获取到的是工厂bean调用getobject创建的对象
  • 2)要获取工厂bean本身,我们需要给id前面加一个前缀“&”
    &colorFactorBean

11 @Bean指定初始化和销毁方法

在Spring中,我们可以自己来指定bean的初始化和销毁的方法。我们指定了bean的初始化和销毁方法之后,当容器在bean进行到当前生命周期的阶段时,会自动调用我们自定义的初始化和销毁方法。

11.1 使用xml配置文件方式

如下:

<bean id="person" class="com.meimeixia.bean.Person" init-method="init" destroy-method="destroy"> <property name="age" value="18"></property> <property name="name" value="liayun"></property> </bean>

需要注意的是:在我们自己写的Person类中,需要存在init()方法和destroy()方法。而且Spring中还规定,这里的init()方法和destroy()方法必须是无参方法,但可以抛出异常。

11.2 使用@Bean注解方式

11.2.1 创建Car类

public class Car { public Car(){ System.out.println("car constructor ..."); } public void init(){ System.out.println("car ... init ... "); } public void detory(){ System.out.println("car ... detory ..."); } }

11.2.2 创建配置类 — MainConfigOfLifeCycle

@Configuration public class MainConfigOfLifeCycle { // @Scope("prototype") @Bean(initMethod = "init",destroyMethod = "detory") public Car car(){ return new Car(); } }

在配置类中将car对象注入spring容器,在bean注解中加入代码,即可实现指定初始化和销毁方法。

11.2.3 创建测试类 — IocTest_LifeCycle

[1] 单实例对象

public class IocTest_LifeCycle { @Test public void test01(){ //1.创建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); System.out.println("ioc容器创建完成..."); //关闭容器后才销毁 applicationContext.close(); } }

测试结果输出:

在这里插入图片描述

对于单实例:

  • 初始化:对象创建完成,并赋值好,调用初始化方法。
  • 销毁:容器关闭的时候。

[2] 多实例对象

那么多实例的情况是如何的呢?

@Configuration public class MainConfigOfLifeCycle { @Scope("prototype") @Bean(initMethod = "init",destroyMethod = "detory") public Car car(){ return new Car(); } }
public class IocTest_LifeCycle { @Test public void test01(){ //1.创建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); System.out.println("ioc容器创建完成..."); //针对多实例 applicationContext.getBean("car"); //关闭容器后才销毁 applicationContext.close(); } }

在这里插入图片描述
对于多实例:

  • 获取bean的时候才进行初始化,可以从上面的测试类中看到我们加入了获取bean实例这一行代码。
  • 容器只会帮你创建bean实例,不会调用销毁方法帮你销毁。

11.3 小结

指定初始化和销毁方法:

  1. xml配置方式:指定init-method 和 destory-method
  2. 通过@Bean注解方式:initMethod 和 destroyMethod

12 使用InitializingBean和DisposableBean来管理bean的生命周期

12.1 接口概述

通过让Bean实现 InitializingBean(定义初始化逻辑),DisposableBean(定义销毁逻辑)。
InitializingBean接口:

package org.springframework.beans.factory; public interface InitializingBean { void afterPropertiesSet() throws Exception; }

afterPropertiesSet()方法是在属性赋好值之后调用的。


DisposableBean接口:

package org.springframework.beans.factory; public interface DisposableBean { void destroy() throws Exception; }

在销毁前,Spring将会调用DisposableBean接口的destroy()方法。

12.2 创建接口实现类 — Cat

@Component public class Cat implements InitializingBean, DisposableBean { public Cat() { System.out.println("cat constructor..."); } //会在bean创建完成,并且属性都赋好值以后进行调用 @Override public void afterPropertiesSet() throws Exception { System.out.println("cat ... afterPropertiesSet ..."); } //会在容器关闭的时候进行调用 @Override public void destroy() throws Exception { System.out.println("cat ... destroy ..."); } }

12.3 测试

[1] 配置类中进行包扫描注册组件

在这里插入图片描述
通过包扫描将Cat类注入spring容器进行管理。

[2] 测试

public class IocTest_LifeCycle { @Test public void test01(){ //1.创建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); System.out.println("ioc容器创建完成..."); //关闭容器后才销毁 applicationContext.close(); } }

在这里插入图片描述

13 @PostConstruct注解和@PreDestroy

13.1 概述

JSR250:

  • @PostConstruct : 在bean创建完成并且属性赋值完成;来执行初始化方法
  • @PreDestroy : 在容器销毁bean之前通知我们进行清理工作

在JDK中还提供了两个注解能够在bean创建完成并且属性赋值完成之后执行一些初始化工作和在容器销毁bean之前通知我们进行一些清理工作。

@PostConstruct注解

@PostConstruct注解被用来修饰一个非静态的void()方法。被@PostConstruct注解修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。@PostConstruct注解修饰的方法通常在构造函数之后,init()方法之前执行。

通常我们是会在Spring框架中使用到@PostConstruct注解的,该注解的方法在整个bean初始化中的执行顺序如下:

Constructor(构造方法)→@Autowired(依赖注入)→@PostConstruct(注释的方法)

@PreDestroy注解

被@PreDestroy注解修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy注解修饰的方法会在destroy()方法之后,Servlet被彻底卸载之前执行。执行顺序如下所示:

调用destroy()方法→@PreDestroy→destroy()方法→bean销毁

13.2 创建Dog类

@Component public class Dog { public Dog(){ System.out.println("Dog ... constructor...."); } //对象创建并赋值之后调用 @PostConstruct public void init(){ System.out.println("Dog ... @PostConstruct..."); } //容器移除对象之前 @PreDestroy public void destroy(){ System.out.println("Dog ... @PreDestroy..."); } }

13.3 测试

public class IocTest_LifeCycle { @Test public void test01(){ //1.创建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); System.out.println("ioc容器创建完成..."); //关闭容器后才销毁 applicationContext.close(); } }

在这里插入图片描述
从结果可以看出被@PostConstruct注解修饰的方法是在bean创建完成并且属性赋值完成之后才执行的,而被@PreDestroy注解修饰的方法是在容器销毁bean之前执行的,通常是进行一些清理工作。

13.4 小结

  • @PostConstruct ---- 对象创建并赋值之后调用
  • @PreDestroy ------ 容器移除对象之前

14 BeanPostProcessor后置处理器

14.1 后置处理器 概述

package org.springframework.beans.factory.config; import org.springframework.beans.BeansException; import org.springframework.lang.Nullable; 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; } }

从源码可以看出,BeanPostProcessor是一个接口,其中有两个方法,即postProcessBeforeInitialization和postProcessAfterInitialization这两个方法,这两个方法分别是在Spring容器中的bean初始化前后执行,所以Spring容器中的每一个bean对象初始化前后,都会执行BeanPostProcessor接口的实现类中的这两个方法。

也就是说,postProcessBeforeInitialization方法会在bean实例化和属性设置之后,自定义初始化方法之前被调用,而postProcessAfterInitialization方法会在自定义初始化方法之后被调用。当容器中存在多个BeanPostProcessor的实现类时,会按照它们在容器中注册的顺序执行。对于自定义的BeanPostProcessor实现类,还可以让其实现Ordered接口自定义排序。

因此我们可以在每个bean对象初始化前后,加上自己的逻辑。实现方式是自定义一个BeanPostProcessor接口的实现类,例如MyBeanPostProcessor,然后在该类的postProcessBeforeInitialization和postProcessAfterInitialization这俩方法中写上自己的逻辑。

14.2 创建MyBeanPostProcessor

/** * 后置处理器:初始化前后进行处理工作 * 将后置处理器加入到容器中:@component */ @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization"+beanName+">"+bean); return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization"+beanName+">"+bean); return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); } }

14.3 测试

public class IocTest_LifeCycle { @Test public void test01(){ //1.创建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); System.out.println("ioc容器创建完成..."); //针对多实例 // applicationContext.getBean("car"); //关闭容器后才销毁 applicationContext.close(); } }

在这里插入图片描述

14.4 小结

BeanPostProcessor[interface]:bean的后置处理器。

在bean初始化前后进行一些处理工作
postProcessBeforeInitialization:在初始化之前工作
postProcessAfterInitialization:在初始化之后工作

在这里插入图片描述

15 @Value — 为属性赋值

package org.springframework.beans.factory.annotation; @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Value { String value(); }

从@Value注解的源码中我们可以看出,@Value注解可以标注在字段、方法、参数以及注解上,而且在程序运行期间生效。

15.1 赋值的几种方式

在这里插入图片描述

对于第三种方式,我们创建一个Person.properties文件。使用${}从其中取出里面的值。
在这里插入图片描述

15.2 测试

@Test public void test01(){ //1.创建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class); printBeans(applicationContext); System.out.println("====================="); Person person = (Person) applicationContext.getBean("person"); System.out.println(person); //也可以从当前环境中获取,因为这些信息都加载到当前环境中了 ConfigurableEnvironment environment = applicationContext.getEnvironment(); String property = environment.getProperty("person.nickName"); System.out.println(property); }

在这里插入图片描述

16 @PropertySource — 加载配置文件

使用 的配置类依旧是上面创建的Person.properties.

源码如下图:
在这里插入图片描述

16.1 未进行配置前测试

public class IocTest_PropertyValue { //抽取出一个通用的打印配置类中所有组件的方法 public void printBeans(AnnotationConfigApplicationContext applicationContext) { String[] definitionNames = applicationContext.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } } @Test public void test01(){ //1.创建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class); printBeans(applicationContext); System.out.println("====================="); Person person = (Person) applicationContext.getBean("person"); System.out.println(person); } }

我们在测试类中测试当前环境中输出person.nickName.测试结果如下:
在这里插入图片描述

如上图没有被加载,取出值。

16.2 使用xml配置方式

在这里插入图片描述

16.3 使用注解方式

16.3.1 创建 配置类 — MainConfigOfPropertyValues

创建一个配置类MainConfigOfPropertyValues

//使用@propertySource读取外部配置文件中的k/v保存到运行的环境变量中; // 加载完外部的配置文件以后使用${}取出配置文件的值 @PropertySource(value={"classpath:/person.properties"}) @Configuration public class MainConfigOfPropertyValues { @Bean public Person person(){ return new Person(); } }

使用@propertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值。

16.3.1 测试

在这里插入图片描述

如上图,看出已经取出。

其实也可以从当前环境中取出配置文件中的值。

public class IocTest_PropertyValue { //抽取出一个通用的打印配置类中所有组件的方法 public void printBeans(AnnotationConfigApplicationContext applicationContext) { String[] definitionNames = applicationContext.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } } @Test public void test01(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class); printBeans(applicationContext); //也可以从当前环境中获取,因为这些信息都加载到当前环境中了 ConfigurableEnvironment environment = applicationContext.getEnvironment(); String property = environment.getProperty("person.nickName"); System.out.println(property); } }

在这里插入图片描述

如上图,我们看到已经从环境中取出配置文件中的值了。

17 @Autowired、@Qualifier、@Primary — 自动装配

自动装配:

  • spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值;

17.1 @Autowired注解

  1. 默认优先按照类型去容器中找对应的组件applicationContext.getBean(BookDao.class)找到就赋值。
  2. 如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找
    applicationContext.getBean("bookDao")
  • BookService bookService(){ BookDao bookDao; }

17.1.1 测试

在BookService类中使用@Autowired注解注入的BookDao对象和直接从IOC容器中获取的BookDao对象是同一个对象。

17.2 @Qualifier注解

@Autowired注解默认是优先按照类型去容器中找对应的组件,找到就赋值;如果找到多个相同类型的组件,那么再将属性的名称作为组件的id,到IOC容器中进行查找。

  1. @Autowired是根据类型进行自动装配的,如果需要按名称进行装配,那么就需要配合@Qualifier注解来使用了。
  2. @Qualifier(“bookDao”):使用@Qualifier指定需要装配的组件的id,而不是使用属性名。
    在这里插入图片描述

17.2.1 测试

如果IOC容器中存在多个相同类型的组件时,那么我们可不可以显示指定@Autowired注解装配哪个组件。
如上图。

17.3 无装配的情况

自动装配默认一定要将属性赋值好,没有就会报错。
可以使用@Autowired(required = false);

在这里插入图片描述
在注解后面加一个这个,表示找到就装配,找不到就算了。因为加入我们不这样设置的话,执行测试程序会报错。(容器中无任何组件,却还要去获取组件)。

{@org.springframework.beans.factory.annotation.Qualifier(value=bookDao), @org.springframework.beans.factory.annotation.Autowired(required=true)}

为@Autowired注解添加属性required=false后,即使IOC容器中没有对应的对象,Spring也不会抛出异常了。不过,此时装配的对象就为null了。

17.4 @Primary注解

在Spring中使用注解时,常常会使用到@Autowired这个注解,它默认是根据类型Type来自动注入的。但有些特殊情况,对同一个接口而言,可能会有几种不同的实现类,而在默认只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先使用哪一个实现类。

@Primary:默认使用首选的bean.也可以使用@Qualifier指定默认使用哪个

在这里插入图片描述
也就是说如果是在没有明确指定的情况下,那么就装配优先级最高的首选的那个bean,如果是在明确指定了的情况下,那么自然就是装配指定的那个bean了。

18 @Resource注解和@Inject注解

spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]

  1. @Resource:
  • 可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的; 不支持@Primary功能,不支持@Autowired(required = false);
  1. @Inject:
  • 需要导入javax.inject的包,和Autowires的功能一样,支持@Primary,只不过接口里没有任何方法(比如说@Autowired(required = false))

18.1 @Resource注解

@Resource注解是Java规范里面的,也可以说它是JSR250规范里面定义的一个注解。该注解默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,那么默认取字段名将其作为组件的名称在IOC容器中进行查找,如果注解写在setter方法上,那么默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的一点是,如果name属性一旦指定,那么就只会按照名称进行装配。
在这里插入图片描述

18.2 @Inject注解

@Inject注解也是Java规范里面的,也可以说它是JSR330规范里面定义的一个注解。该注解默认是根据参数名去寻找bean注入,支持Spring的@Primary注解优先注入,@Inject注解还可以增加@Named注解指定要注入的bean。

在这里插入图片描述
要想使用@Inject注解,需要在项目的pom.xml文件中添加如下依赖,即导入javax.inject这个包。

<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>

18.3 小结

@Resource和@Inject这俩注解与@Autowired注解的区别

  1. 不同点
  • @Autowired是Spring中的专有注解,而@Resource是Java中JSR250规范里面定义的一个注解,@Inject是Java中JSR330规范里面定义的一个注解
  • @Autowired支持参数required=false,而@Resource和@Inject都不支持
  • @Autowired和@Inject支持@Primary注解优先注入,而@Resource不支持
  • @Autowired通过@Qualifier指定注入特定bean,@Resource可以通过参数name指定注入bean,而@Inject需要通过@Named注解指定注入bean
  1. 相同点
    三种注解都可以实现bean的自动装配。

19 构造器、参数、方法和属性的自动装配

19.1 概述

package org.springframework.beans.factory.annotation; @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { boolean required() default true; }

可以看出@Autowired注解不仅可以标注在字段上,而且还可以标注在构造方法、实例方法以及参数上。

19.2 测试

//默认加在Ioc 容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作 @Component public class Boss { private Car car; public Car getCar() { return car; } // @Autowired //标注在构造器中的,也是从容器中获取Car这个组件 public Boss(@Autowired Car car) { this.car = car; System.out.println("BOSS ... 有参构造器"); } @Autowired //标注在方法上,spring容器创建当前对象,就会调用方法,完成赋值。 //方法使用的参数,自定义类型的值从Ioc容器中获取。 public void setCar(Car car) { this.car = car; } @Override public String toString() { return "Boss{" + "car=" + car + '}'; } }
  • 当@Autowired注解标注在方法上时,Spring容器在创建当前对象的时候,就会调用相应的方法为对象赋值。如果标注的方法存在参数时,那么方法使用的参数和自定义类型的值,需要从IOC容器中获取。

  • 使用@Autowired注解标注在构造方法上时,构造方法中的参数对象也是从IOC容器中获取的.

注意:使用@Autowired注解标注在构造方法上时,如果组件中只有一个有参构造方法,那么这个有参构造方法上的@Autowired注解可以省略,并且参数位置的组件还是可以自动从IOC容器中获取。

19.3 小结

无论@Autowired注解是标注在字段上、实例方法上、构造方法上还是参数上,参数位置的组件都是从IOC容器中获取。

  1. 如果方法只有一个IOC容器中的对象作为参数,当@Autowired注解标注在这个方法的参数上时,我们可以将@Autowired注解省略掉。也就说@Bean注解标注的方法在创建对象的时候,方法参数的值是从IOC容器中获取的,此外,标注在这个方法的参数上的@Autowired注解可以省略。

  2. 其实,我们用到最多的还是把@Autowired注解标注在方法位置,即使用@Bean注解+方法参数这种形式,此时,该方法参数的值从IOC容器中获取,并且还可以默认不写@Autowired注解,因为效果都是一样的,都能实现自动装配!

20 自定义组件想要使用Spring容器底层的一些组件

20.1 概述与测试

@Component public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("传入的Ioc:"+applicationContext); this.applicationContext = applicationContext; } @Override public void setBeanName(String s) { System.out.println("当前bean的名字:"+ s); } @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { String resolveStringValue = resolver.resolveStringValue("你好 ${os.name} #{20*18}"); System.out.println("解析的字符串"+resolveStringValue); } }
  • 实现一个BeanNameAware接口,我们可以通过BeanNameAware接口获取到当前bean在Spring容器中的名称
  • 实现一个EmbeddedValueResolverAware接口,我们通过EmbeddedValueResolverAware接口能够获取到String值解析器

值解析器:用来帮我们解析那些String类型的值的,如果这个String类型的值里面有一些占位符,那么也会帮我们把这些占位符给解析出来,最后返回一个解析后的值。

小结
xxAware接口的底层原理是由XxxAwareProcessor实现类实现的,也就是说每一个XxxAware接口都有它自己对应的XxxAwareProcessor实现类。

例如,我们这里以ApplicationContextAware接口为例,ApplicationContextAware接口的底层原理就是由ApplicationContextAwareProcessor类实现的。从ApplicationContextAwareProcessor类的源码可以看出,其实现了BeanPostProcessor接口,本质上是一个后置处理器。

自定义组件实现xxxAware,在创建对象的时候,会调用接口规定的方法注入相关组件,Aware;

  • 把spring底层一些组件注入到自定义的Bean中;
  • xxxAware:功能使用XXXProcessor;
    • ApplicationContext == > ApplicationContextProcessor;

21 @Profile — 环境配置与切换

21.1 概述

在实际的企业开发环境中,往往都会将环境分为开发环境、测试环境和生产环境,并且每个环境基本上都是互相隔离的,也就是说,开发环境、测试环境和生产环境它们之间是互不相通的。在以前的开发过程中,如果开发人员完成相应的功能模块并通过单元测试后,那么他会通过手动修改配置文件的形式,将项目的配置修改成测试环境,发布到测试环境中进行测试。测试通过后,再将配置修改为生产环境,发布到生产环境中。这样手动修改配置的方式,不仅增加了开发和运维的工作量,而且总是手工修改各项配置文件会很容易出问题。那么,有没有什么方式可以解决这些问题呢?答案是:有!通过@Profile注解就可以完全做到这点。

在容器中如果存在同一类型的多个组件,那么可以使用@Profile注解标识要获取的是哪一个bean。也可以说@Profile注解是Spring为我们提供的可以根据当前环境,动态地激活和切换一系列组件的功能。这个功能在不同的环境使用不同的变量的情景下特别有用,例如,开发环境、测试环境、生产环境使用不同的数据源,在不改变代码的情况下,可以使用这个注解来动态地切换要连接的数据库。

在这里插入图片描述

@Profile注解不仅可以标注在方法上,也可以标注在配置类上。

  1. 如果@Profile注解标注在配置类上,那么只有是在指定的环境的时候,整个配置类里面的所有配置才会生效。
  2. 如果一个bean上没有使用@Profile注解进行标注,那么这个bean在任何环境下都会被注册到IOC容器中,当然了,前提是在整个配置类生效的情况下。

21.2 引入德鲁伊数据源

<!--测试@Profile注解--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>

21.3 创建jdbc.properties

jdbc.user=root;
jdbc.password=abc123;
jdbc.driverClassName=com.mysql.jdbc.Driver

21.4 编写配置类

读取配置文件的方式:
1.
在这里插入图片描述
2. 在这里插入图片描述

  1. 继承 EmbeddedValueResolverAware·接口在这里插入图片描述
    在这里插入图片描述
// /表示从类路径下开始寻找 @PropertySource("classpath:/jdbc.properties") @Configuration //@Profile("prod") 加载在配置类上,对整个类都有管理作用 public class MainConfigOfProfile implements EmbeddedValueResolverAware { // 使用jdbc.properties的几种方式方式,取出其中值 //方式1 使用属性+ @Value注解 @Value("${jdbc.user}") private String user; //方式三 :使用值解析器 private StringValueResolver valueResolver; private String driverClass; // @Profile("test") @Bean public Yellow yellow(){ return new Yellow(); } @Profile("test") @Bean("testDataSource") //方式2 使用参数+@Value注解 public DataSource dataSourceTest(@Value("${jdbc.password}") String pwd) { DruidDataSource dataSource = new DruidDataSource(); // dataSource.setUsername("user"); 方式1 dataSource.setUsername(user); // dataSource.setPassword("abc123"); 方式2 dataSource.setPassword(pwd); // dataSource.setDriverClassName("com.mysql.jdbc.Driver"); 方式3 dataSource.setDriverClassName(driverClass); dataSource.setUrl("jdbc:mysql://localhost:13306/test"); return dataSource; } @Profile("dev") @Bean("DevDataSource") public DataSource dataSourceDev(@Value("${jdbc.password}") String pwd) { DruidDataSource dataSource = new DruidDataSource(); // dataSource.setUsername("user"); dataSource.setUsername(user); // dataSource.setPassword("abc123"); dataSource.setPassword(pwd); // dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setDriverClassName(driverClass); dataSource.setUrl("jdbc:mysql://localhost:13306/atguigudb"); return dataSource; } @Profile("prod") @Bean("ProdDataSource") public DataSource dataSourceProd(@Value("${jdbc.password}") String pwd) { DruidDataSource dataSource = new DruidDataSource(); // dataSource.setUsername("user"); dataSource.setUsername(user); // dataSource.setPassword("abc123"); dataSource.setPassword(pwd); // dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setDriverClassName(driverClass); dataSource.setUrl("jdbc:mysql://localhost:13306/bookdb"); return dataSource; } @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { this.valueResolver = resolver; driverClass = valueResolver.resolveStringValue("${jdbc.driverClassName}"); } }

21.5 测试

激活环境两种方式:

  1. 运行时在命令行参数中动态设置:虚拟机参数位置加载:
  • -Dspring.profiles.active=test (dev prod)
  1. 使用代码方式

其实主要是通过AnnotationConfigApplicationContext类的无参构造方法来实现,具体步骤如下:

  1. 在bean上加@Profile注解,其value属性值为环境标识,可以自定义
  2. 使用AnnotationConfigApplicationContext类的无参构造方法创建容器
  3. 设置容器环境,其值为第1步设置的环境标识
  4. 设置容器的配置类
  5. 刷新容器
public class IocTest_Profile { //1.运行时在命令行参数中动态设置:虚拟机参数位置加载:-Dspring.profiles.active=test (dev prod) @Test public void test01(){ // AnnotationConfigApplicationContext applicationContext = // new AnnotationConfigApplicationContext(MainConfigOfProfile.class); // 方式2 使用代码方式 // 1.创建一个applicationContext AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); //2.设置需要激活的环境 applicationContext.getEnvironment().setActiveProfiles("test","dev"); //3.注册配置类 applicationContext.register(MainConfigOfProfile.class); //4.启动刷新容器 applicationContext.refresh(); //方式2 Yellow bean = applicationContext.getBean(Yellow.class); System.out.println(bean); String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class); for (String name : namesForType){ System.out.println(name); } } }

22 搭建AOP测试环境

22.1 引入依赖

<!--aop依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.21</version> </dependency>

22.2 定义目标类

public class MathCalculator { public int div(int i,int j){ System.out.println("方法被调用......"); return i/j; } }

22.3 定义切面类

@Aspect // 告诉spring这是一个切面类 public class LogAspects { /** * 抽取公共的切入点表达式 * 1.在本类引用 ------ @Before("pointCut()") 写类名 * 2.其他的切面引用 ------ @After("com.atguigu.aop.LogAspects.pointCut()") 写全类名 * */ @Pointcut("execution(public int com.atguigu.aop.MathCalculator.*(..))") public void pointCut(){}; //@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入) // @Before("public int com.atguigu.aop.MathCalculator.*(..)") @Before("pointCut()") public void logStart(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); // 拿到参数列表 // System.out.println("除法运行。。。@Before参数列表是:{}"); System.out.println(joinPoint.getSignature().getName()+"运行...@Before,参数列表是:{"+ Arrays.asList(args)+"}"); } @After("com.atguigu.aop.LogAspects.pointCut()") public void logEnd(JoinPoint joinPoint){ // System.out.println("除法结束@After..."); System.out.println(joinPoint.getSignature().getName()+"结束......@After"); } // @AfterReturning("pointCut()") @AfterReturning(value="pointCut()",returning = "result") //returning 来指定我们这个方法的参数谁来封装返回值 // JoinPoint joinPoint ----必须出现在第一参数位置,否则运行报错 public void logReturn(JoinPoint joinPoint,Object result){ // System.out.println("除法正常返回...@AfterReturning运行结果是:{}"); System.out.println(joinPoint.getSignature().getName()+"除法正常返回...@AfterReturning运行结果是:{"+result+"}"); } // @AfterThrowing("pointCut()") @AfterThrowing(value="pointCut()",throwing = "exception") public void logException(JoinPoint joinPoint,Exception exception){ // System.out.println("除法异常...@AfterThrowing异常信息:{}"); System.out.println(joinPoint.getSignature().getName()+"除法异常...@AfterThrowing异常信息:{"+exception+"}"); } }

通知的几种类型:

  • 前置通知(对应的注解是@Before):在目标方法运行之前运行
  • 后置通知(对应的注解是@After):在目标方法运行结束之后运行,无论目标方法是正常结束还是异常结束都会执行
  • 返回通知(对应的注解是@AfterReturning):在目标方法正常返回之后运行
  • 异常通知(对应的注解是@AfterThrowing):在目标方法运行出现异常之后运行
  • 环绕通知(对应的注解是@Around):动态代理,我们可以直接手动推进目标方法运行(joinPoint.procced())

切入点表达式:

public int com.atguigu.aop.MathCalculator.*(…)

抽取切入点表达式:需要在类上加入@Pointcut这个注解。

22.4 创建配置类

@Configuration @EnableAspectJAutoProxy public class MainConfigOfAOP { //业务逻辑加入到容器中 @Bean public MathCalculator mathCalculator(){ return new MathCalculator(); } //切面类 @Bean public LogAspects logAspects(){ return new LogAspects(); } }

@EnableAspectJAutoProxy注意加入这个注解。

22.5 测试

public class aopTest { @Test public void test01(){ //1.创建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class); //使用spring容器中的组件才会有spring中的功能 MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class); mathCalculator.div(1,1); //测试出现异常 // mathCalculator.div(1,0); applicationContext.close(); } }

22.6 小结

搭建AOP测试环境时,虽然步骤繁多,但是我们只要牢牢记住以下三点,就会无往而不利了。

  1. 将切面类和业务逻辑组件(目标方法所在类)都加入到容器中,并且要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)。
  2. 在切面类上的每个通知方法上标注通知注解,告诉Spring何时何地运行,当然最主要的是要写好切入点表达式,这个切入点表达式可以参照官方文档来写。
  3. 开启基于注解的AOP模式,即加上@EnableAspectJAutoProxy注解,这是最关键的一点。

22.7 AOP动态代理原理

/** * AOP:【动态代理】 * 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式; * * 1、导入aop模块;Spring AOP:(spring-aspects) * 2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx) * 3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行; * 通知方法: * 前置通知(@Before):logStart:在目标方法(div)运行之前运行 * 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束) * 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行 * 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行 * 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced()) * 4、给切面类的目标方法标注何时何地运行(通知注解); * 5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中; * 6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect) * [7]、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】 * 在Spring中很多的 @EnableXXX; * * 三步: * 1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect * 2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式) * 3)、开启基于注解的aop模式;@EnableAspectJAutoProxy * * AOP原理:【看给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么?】 * @EnableAspectJAutoProxy * 1、@EnableAspectJAutoProxy是什么? * @Import(AspectJAutoProxyRegistrar.class):给容器中导入AspectJAutoProxyRegistrar * 利用AspectJAutoProxyRegistrar自定义给容器中注册bean;BeanDefinetion * internalAutoProxyCreator=AnnotationAwareAspectJAutoProxyCreator * * 给容器中注册一个AnnotationAwareAspectJAutoProxyCreator; * * 2、 AnnotationAwareAspectJAutoProxyCreator: * AnnotationAwareAspectJAutoProxyCreator * ->AspectJAwareAdvisorAutoProxyCreator * ->AbstractAdvisorAutoProxyCreator * ->AbstractAutoProxyCreator * implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware * 关注后置处理器(在bean初始化完成前后做事情)、自动装配BeanFactory * * AbstractAutoProxyCreator.setBeanFactory() * AbstractAutoProxyCreator.有后置处理器的逻辑; * * AbstractAdvisorAutoProxyCreator.setBeanFactory()-》initBeanFactory() * * AnnotationAwareAspectJAutoProxyCreator.initBeanFactory() * * * 流程: * 1)、传入配置类,创建ioc容器 * 2)、注册配置类,调用refresh()刷新容器; * 3)、registerBeanPostProcessors(beanFactory);注册bean的后置处理器来方便拦截bean的创建; * 1)、先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor * 2)、给容器中加别的BeanPostProcessor * 3)、优先注册实现了PriorityOrdered接口的BeanPostProcessor; * 4)、再给容器中注册实现了Ordered接口的BeanPostProcessor; * 5)、注册没实现优先级接口的BeanPostProcessor; * 6)、注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中; * 创建internalAutoProxyCreator的BeanPostProcessor【AnnotationAwareAspectJAutoProxyCreator】 * 1)、创建Bean的实例 * 2)、populateBean;给bean的各种属性赋值 * 3)、initializeBean:初始化bean; * 1)、invokeAwareMethods():处理Aware接口的方法回调 * 2)、applyBeanPostProcessorsBeforeInitialization():应用后置处理器的postProcessBeforeInitialization() * 3)、invokeInitMethods();执行自定义的初始化方法 * 4)、applyBeanPostProcessorsAfterInitialization();执行后置处理器的postProcessAfterInitialization(); * 4)、BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功;--》aspectJAdvisorsBuilder * 7)、把BeanPostProcessor注册到BeanFactory中; * beanFactory.addBeanPostProcessor(postProcessor); * =======以上是创建和注册AnnotationAwareAspectJAutoProxyCreator的过程======== * * AnnotationAwareAspectJAutoProxyCreator => InstantiationAwareBeanPostProcessor * 4)、finishBeanFactoryInitialization(beanFactory);完成BeanFactory初始化工作;创建剩下的单实例bean * 1)、遍历获取容器中所有的Bean,依次创建对象getBean(beanName); * getBean->doGetBean()->getSingleton()-> * 2)、创建bean * 【AnnotationAwareAspectJAutoProxyCreator在所有bean创建之前会有一个拦截,InstantiationAwareBeanPostProcessor,会调用postProcessBeforeInstantiation()】 * 1)、先从缓存中获取当前bean,如果能获取到,说明bean是之前被创建过的,直接使用,否则再创建; * 只要创建好的Bean都会被缓存起来 * 2)、createBean();创建bean; * AnnotationAwareAspectJAutoProxyCreator 会在任何bean创建之前先尝试返回bean的实例 * 【BeanPostProcessor是在Bean对象创建完成初始化前后调用的】 * 【InstantiationAwareBeanPostProcessor是在创建Bean实例之前先尝试用后置处理器返回对象的】 * 1)、resolveBeforeInstantiation(beanName, mbdToUse);解析BeforeInstantiation * 希望后置处理器在此能返回一个代理对象;如果能返回代理对象就使用,如果不能就继续 * 1)、后置处理器先尝试返回对象; * bean = applyBeanPostProcessorsBeforeInstantiation(): * 拿到所有后置处理器,如果是InstantiationAwareBeanPostProcessor; * 就执行postProcessBeforeInstantiation * if (bean != null) { bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); } * * 2)、doCreateBean(beanName, mbdToUse, args);真正的去创建一个bean实例;和3.6流程一样; * 3)、 * * * AnnotationAwareAspectJAutoProxyCreator【InstantiationAwareBeanPostProcessor】 的作用: * 1)、每一个bean创建之前,调用postProcessBeforeInstantiation(); * 关心MathCalculator和LogAspect的创建 * 1)、判断当前bean是否在advisedBeans中(保存了所有需要增强bean) * 2)、判断当前bean是否是基础类型的Advice、Pointcut、Advisor、AopInfrastructureBean, * 或者是否是切面(@Aspect * 3)、是否需要跳过 * 1)、获取候选的增强器(切面里面的通知方法)【List<Advisor> candidateAdvisors】 * 每一个封装的通知方法的增强器是 InstantiationModelAwarePointcutAdvisor; * 判断每一个增强器是否是 AspectJPointcutAdvisor 类型的;返回true * 2)、永远返回false * * 2)、创建对象 * postProcessAfterInitialization; * return wrapIfNecessary(bean, beanName, cacheKey);//包装如果需要的情况下 * 1)、获取当前bean的所有增强器(通知方法) Object[] specificInterceptors * 1、找到候选的所有的增强器(找哪些通知方法是需要切入当前bean方法的) * 2、获取到能在bean使用的增强器。 * 3、给增强器排序 * 2)、保存当前bean在advisedBeans中; * 3)、如果当前bean需要增强,创建当前bean的代理对象; * 1)、获取所有增强器(通知方法) * 2)、保存到proxyFactory * 3)、创建代理对象:Spring自动决定 * JdkDynamicAopProxy(config);jdk动态代理; * ObjenesisCglibAopProxy(config);cglib的动态代理; * 4)、给容器中返回当前组件使用cglib增强了的代理对象; * 5)、以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程; * * * 3)、目标方法执行 ; * 容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx); * 1)、CglibAopProxy.intercept();拦截目标方法的执行 * 2)、根据ProxyFactory对象获取将要执行的目标方法拦截器链; * List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); * 1)、List<Object> interceptorList保存所有拦截器 5 * 一个默认的ExposeInvocationInterceptor 和 4个增强器; * 2)、遍历所有的增强器,将其转为Interceptor; * registry.getInterceptors(advisor); * 3)、将增强器转为List<MethodInterceptor>; * 如果是MethodInterceptor,直接加入到集合中 * 如果不是,使用AdvisorAdapter将增强器转为MethodInterceptor; * 转换完成返回MethodInterceptor数组; * * 3)、如果没有拦截器链,直接执行目标方法; * 拦截器链(每一个通知方法又被包装为方法拦截器,利用MethodInterceptor机制) * 4)、如果有拦截器链,把需要执行的目标对象,目标方法, * 拦截器链等信息传入创建一个 CglibMethodInvocation 对象, * 并调用 Object retVal = mi.proceed(); * 5)、拦截器链的触发过程; * 1)、如果没有拦截器执行执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法; * 2)、链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行; * 拦截器链的机制,保证通知方法与目标方法的执行顺序; * * 总结: * 1)、 @EnableAspectJAutoProxy 开启AOP功能 * 2)、 @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator * 3)、AnnotationAwareAspectJAutoProxyCreator是一个后置处理器; * 4)、容器的创建流程: * 1)、registerBeanPostProcessors()注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator对象 * 2)、finishBeanFactoryInitialization()初始化剩下的单实例bean * 1)、创建业务逻辑组件和切面组件 * 2)、AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程 * 3)、组件创建完之后,判断组件是否需要增强 * 是:切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib); * 5)、执行目标方法: * 1)、代理对象执行目标方法 * 2)、CglibAopProxy.intercept(); * 1)、得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor) * 2)、利用拦截器的链式机制,依次进入每一个拦截器进行执行; * 3)、效果: * 正常执行:前置通知-》目标方法-》后置通知-》返回通知 * 出现异常:前置通知-》目标方法-》后置通知-》异常通知

23 扩展原理

/** * 扩展原理: * BeanPostProcessor:bean后置处理器,bean创建对象初始化前后进行拦截工作的 * * 1、BeanFactoryPostProcessor:beanFactory的后置处理器; * 在BeanFactory标准初始化之后调用,来定制和修改BeanFactory的内容; * 所有的bean定义已经保存加载到beanFactory,但是bean的实例还未创建 * * * BeanFactoryPostProcessor原理: * 1)、ioc容器创建对象 * 2)、invokeBeanFactoryPostProcessors(beanFactory); * 如何找到所有的BeanFactoryPostProcessor并执行他们的方法; * 1)、直接在BeanFactory中找到所有类型是BeanFactoryPostProcessor的组件,并执行他们的方法 * 2)、在初始化创建其他组件前面执行 * * 2、BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor * postProcessBeanDefinitionRegistry(); * 在所有bean定义信息将要被加载,bean实例还未创建的; * * 优先于BeanFactoryPostProcessor执行; * 利用BeanDefinitionRegistryPostProcessor给容器中再额外添加一些组件; * * 原理: * 1)、ioc创建对象 * 2)、refresh()-》invokeBeanFactoryPostProcessors(beanFactory); * 3)、从容器中获取到所有的BeanDefinitionRegistryPostProcessor组件。 * 1、依次触发所有的postProcessBeanDefinitionRegistry()方法 * 2、再来触发postProcessBeanFactory()方法BeanFactoryPostProcessor; * * 4)、再来从容器中找到BeanFactoryPostProcessor组件;然后依次触发postProcessBeanFactory()方法 * * 3、ApplicationListener:监听容器中发布的事件。事件驱动模型开发; * public interface ApplicationListener<E extends ApplicationEvent> * 监听 ApplicationEvent 及其下面的子事件; * * 步骤: * 1)、写一个监听器(ApplicationListener实现类)来监听某个事件(ApplicationEvent及其子类) * @EventListener; * 原理:使用EventListenerMethodProcessor处理器来解析方法上的@EventListener * * 2)、把监听器加入到容器; * 3)、只要容器中有相关事件的发布,我们就能监听到这个事件; * ContextRefreshedEvent:容器刷新完成(所有bean都完全创建)会发布这个事件; * ContextClosedEvent:关闭容器会发布这个事件; * 4)、发布一个事件: * applicationContext.publishEvent(); * * 原理: * ContextRefreshedEvent、IOCTest_Ext$1[source=我发布的时间]、ContextClosedEvent; * 1)、ContextRefreshedEvent事件: * 1)、容器创建对象:refresh(); * 2)、finishRefresh();容器刷新完成会发布ContextRefreshedEvent事件 * 2)、自己发布事件; * 3)、容器关闭会发布ContextClosedEvent; * * 【事件发布流程】: * 3)、publishEvent(new ContextRefreshedEvent(this)); * 1)、获取事件的多播器(派发器):getApplicationEventMulticaster() * 2)、multicastEvent派发事件: * 3)、获取到所有的ApplicationListener; * for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { * 1)、如果有Executor,可以支持使用Executor进行异步派发; * Executor executor = getTaskExecutor(); * 2)、否则,同步的方式直接执行listener方法;invokeListener(listener, event); * 拿到listener回调onApplicationEvent方法; * * 【事件多播器(派发器)】 * 1)、容器创建对象:refresh(); * 2)、initApplicationEventMulticaster();初始化ApplicationEventMulticaster; * 1)、先去容器中找有没有id=“applicationEventMulticaster”的组件; * 2)、如果没有this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); * 并且加入到容器中,我们就可以在其他组件要派发事件,自动注入这个applicationEventMulticaster; * * 【容器中有哪些监听器】 * 1)、容器创建对象:refresh(); * 2)、registerListeners(); * 从容器中拿到所有的监听器,把他们注册到applicationEventMulticaster中; * String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); * //将listener注册到ApplicationEventMulticaster中 * getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); * * SmartInitializingSingleton 原理:->afterSingletonsInstantiated(); * 1)、ioc容器创建对象并refresh(); * 2)、finishBeanFactoryInitialization(beanFactory);初始化剩下的单实例bean; * 1)、先创建所有的单实例bean;getBean(); * 2)、获取所有创建好的单实例bean,判断是否是SmartInitializingSingleton类型的; * 如果是就调用afterSingletonsInstantiated(); * * * */

24 ioc容器创建原理

Spring容器的refresh()【创建刷新】; 1、prepareRefresh()刷新前的预处理; 1)、initPropertySources()初始化一些属性设置;子类自定义个性化的属性设置方法; 2)、getEnvironment().validateRequiredProperties();检验属性的合法等 3)、earlyApplicationEvents= new LinkedHashSet<ApplicationEvent>();保存容器中的一些早期的事件; 2、obtainFreshBeanFactory();获取BeanFactory; 1)、refreshBeanFactory();刷新【创建】BeanFactory; 创建了一个this.beanFactory = new DefaultListableBeanFactory(); 设置id; 2)、getBeanFactory();返回刚才GenericApplicationContext创建的BeanFactory对象; 3)、将创建的BeanFactory【DefaultListableBeanFactory】返回; 3、prepareBeanFactory(beanFactory);BeanFactory的预准备工作(BeanFactory进行一些设置); 1)、设置BeanFactory的类加载器、支持表达式解析器... 2)、添加部分BeanPostProcessor【ApplicationContextAwareProcessor】 3)、设置忽略的自动装配的接口EnvironmentAware、EmbeddedValueResolverAware、xxx; 4)、注册可以解析的自动装配;我们能直接在任何组件中自动注入: BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext 5)、添加BeanPostProcessor【ApplicationListenerDetector】 6)、添加编译时的AspectJ; 7)、给BeanFactory中注册一些能用的组件; environment【ConfigurableEnvironment】、 systemProperties【Map<String, Object>】、 systemEnvironment【Map<String, Object>】 4、postProcessBeanFactory(beanFactory);BeanFactory准备工作完成后进行的后置处理工作; 1)、子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置 ======================以上是BeanFactory的创建及预准备工作================================== 5、invokeBeanFactoryPostProcessors(beanFactory);执行BeanFactoryPostProcessor的方法; BeanFactoryPostProcessor:BeanFactory的后置处理器。在BeanFactory标准初始化之后执行的; 两个接口:BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor 1)、执行BeanFactoryPostProcessor的方法; 先执行BeanDefinitionRegistryPostProcessor 1)、获取所有的BeanDefinitionRegistryPostProcessor; 2)、看先执行实现了PriorityOrdered优先级接口的BeanDefinitionRegistryPostProcessor、 postProcessor.postProcessBeanDefinitionRegistry(registry) 3)、在执行实现了Ordered顺序接口的BeanDefinitionRegistryPostProcessor; postProcessor.postProcessBeanDefinitionRegistry(registry) 4)、最后执行没有实现任何优先级或者是顺序接口的BeanDefinitionRegistryPostProcessors; postProcessor.postProcessBeanDefinitionRegistry(registry) 再执行BeanFactoryPostProcessor的方法 1)、获取所有的BeanFactoryPostProcessor 2)、看先执行实现了PriorityOrdered优先级接口的BeanFactoryPostProcessor、 postProcessor.postProcessBeanFactory() 3)、在执行实现了Ordered顺序接口的BeanFactoryPostProcessor; postProcessor.postProcessBeanFactory() 4)、最后执行没有实现任何优先级或者是顺序接口的BeanFactoryPostProcessor; postProcessor.postProcessBeanFactory() 6、registerBeanPostProcessors(beanFactory);注册BeanPostProcessor(Bean的后置处理器)【 intercept bean creation】 不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的 BeanPostProcessor、 DestructionAwareBeanPostProcessor、 InstantiationAwareBeanPostProcessor、 SmartInstantiationAwareBeanPostProcessor、 MergedBeanDefinitionPostProcessor【internalPostProcessors】、 1)、获取所有的 BeanPostProcessor;后置处理器都默认可以通过PriorityOrdered、Ordered接口来执行优先级 2)、先注册PriorityOrdered优先级接口的BeanPostProcessor; 把每一个BeanPostProcessor;添加到BeanFactory中 beanFactory.addBeanPostProcessor(postProcessor); 3)、再注册Ordered接口的 4)、最后注册没有实现任何优先级接口的 5)、最终注册MergedBeanDefinitionPostProcessor; 6)、注册一个ApplicationListenerDetector;来在Bean创建完成后检查是否是ApplicationListener,如果是 applicationContext.addApplicationListener((ApplicationListener<?>) bean); 7、initMessageSource();初始化MessageSource组件(做国际化功能;消息绑定,消息解析); 1)、获取BeanFactory 2)、看容器中是否有id为messageSource的,类型是MessageSource的组件 如果有赋值给messageSource,如果没有自己创建一个DelegatingMessageSource; MessageSource:取出国际化配置文件中的某个key的值;能按照区域信息获取; 3)、把创建好的MessageSource注册在容器中,以后获取国际化配置文件的值的时候,可以自动注入MessageSource; beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource); MessageSource.getMessage(String code, Object[] args, String defaultMessage, Locale locale); 8、initApplicationEventMulticaster();初始化事件派发器; 1)、获取BeanFactory 2)、从BeanFactory中获取applicationEventMulticaster的ApplicationEventMulticaster; 3)、如果上一步没有配置;创建一个SimpleApplicationEventMulticaster 4)、将创建的ApplicationEventMulticaster添加到BeanFactory中,以后其他组件直接自动注入 9、onRefresh();留给子容器(子类) 1、子类重写这个方法,在容器刷新的时候可以自定义逻辑; 10、registerListeners();给容器中将所有项目里面的ApplicationListener注册进来; 1、从容器中拿到所有的ApplicationListener 2、将每个监听器添加到事件派发器中; getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); 3、派发之前步骤产生的事件; 11、finishBeanFactoryInitialization(beanFactory);初始化所有剩下的单实例bean; 1、beanFactory.preInstantiateSingletons();初始化后剩下的单实例bean 1)、获取容器中的所有Bean,依次进行初始化和创建对象 2)、获取Bean的定义信息;RootBeanDefinition 3)、Bean不是抽象的,是单实例的,是懒加载; 1)、判断是否是FactoryBean;是否是实现FactoryBean接口的Bean; 2)、不是工厂Bean。利用getBean(beanName);创建对象 0、getBean(beanName); ioc.getBean(); 1、doGetBean(name, null, null, false); 2、先获取缓存中保存的单实例Bean。如果能获取到说明这个Bean之前被创建过(所有创建过的单实例Bean都会被缓存起来) private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);获取的 3、缓存中获取不到,开始Bean的创建对象流程; 4、标记当前bean已经被创建 5、获取Bean的定义信息; 6、【获取当前Bean依赖的其他Bean;如果有按照getBean()把依赖的Bean先创建出来;】 7、启动单实例Bean的创建流程; 1)、createBean(beanName, mbd, args); 2)、Object bean = resolveBeforeInstantiation(beanName, mbdToUse);让BeanPostProcessor先拦截返回代理对象; 【InstantiationAwareBeanPostProcessor】:提前执行; 先触发:postProcessBeforeInstantiation(); 如果有返回值:触发postProcessAfterInitialization(); 3)、如果前面的InstantiationAwareBeanPostProcessor没有返回代理对象;调用4 4)、Object beanInstance = doCreateBean(beanName, mbdToUse, args);创建Bean 1)、【创建Bean实例】;createBeanInstance(beanName, mbd, args); 利用工厂方法或者对象的构造器创建出Bean实例; 2)、applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); 调用MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition(mbd, beanType, beanName); 3)、【Bean属性赋值】populateBean(beanName, mbd, instanceWrapper); 赋值之前: 1)、拿到InstantiationAwareBeanPostProcessor后置处理器; postProcessAfterInstantiation(); 2)、拿到InstantiationAwareBeanPostProcessor后置处理器; postProcessPropertyValues(); =====赋值之前:=== 3)、应用Bean属性的值;为属性利用setter方法等进行赋值; applyPropertyValues(beanName, mbd, bw, pvs); 4)、【Bean初始化】initializeBean(beanName, exposedObject, mbd); 1)、【执行Aware接口方法】invokeAwareMethods(beanName, bean);执行xxxAware接口的方法 BeanNameAware\BeanClassLoaderAware\BeanFactoryAware 2)、【执行后置处理器初始化之前】applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); BeanPostProcessor.postProcessBeforeInitialization(); 3)、【执行初始化方法】invokeInitMethods(beanName, wrappedBean, mbd); 1)、是否是InitializingBean接口的实现;执行接口规定的初始化; 2)、是否自定义初始化方法; 4)、【执行后置处理器初始化之后】applyBeanPostProcessorsAfterInitialization BeanPostProcessor.postProcessAfterInitialization(); 5)、注册Bean的销毁方法; 5)、将创建的Bean添加到缓存中singletonObjects; ioc容器就是这些Map;很多的Map里面保存了单实例Bean,环境信息。。。。; 所有Bean都利用getBean创建完成以后; 检查所有的Bean是否是SmartInitializingSingleton接口的;如果是;就执行afterSingletonsInstantiated(); 12、finishRefresh();完成BeanFactory的初始化创建工作;IOC容器就创建完成; 1)、initLifecycleProcessor();初始化和生命周期有关的后置处理器;LifecycleProcessor 默认从容器中找是否有lifecycleProcessor的组件【LifecycleProcessor】;如果没有new DefaultLifecycleProcessor(); 加入到容器; 写一个LifecycleProcessor的实现类,可以在BeanFactory void onRefresh(); void onClose(); 2)、 getLifecycleProcessor().onRefresh(); 拿到前面定义的生命周期处理器(BeanFactory);回调onRefresh(); 3)、publishEvent(new ContextRefreshedEvent(this));发布容器刷新完成事件; 4)、liveBeansView.registerApplicationContext(this); ======总结=========== 1)、Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息; 1)、xml注册bean;<bean> 2)、注解注册Bean;@Service@Component@Bean、xxx 2)、Spring容器会合适的时机创建这些Bean 1)、用到这个bean的时候;利用getBean创建bean;创建好以后保存在容器中; 2)、统一创建剩下所有的bean的时候;finishBeanFactoryInitialization(); 3)、后置处理器;BeanPostProcessor 1)、每一个bean创建完成,都会使用各种后置处理器进行处理;来增强bean的功能; AutowiredAnnotationBeanPostProcessor:处理自动注入 AnnotationAwareAspectJAutoProxyCreator:来做AOP功能; xxx.... 增强的功能注解: AsyncAnnotationBeanPostProcessor .... 4)、事件驱动模型; ApplicationListener;事件监听; ApplicationEventMulticaster;事件派发:

25 spring源码总结

Spring容器的refresh()【创建刷新】; 1、prepareRefresh()刷新前的预处理; 1)、initPropertySources()初始化一些属性设置;子类自定义个性化的属性设置方法; 2)、getEnvironment().validateRequiredProperties();检验属性的合法等 3)、earlyApplicationEvents= new LinkedHashSet<ApplicationEvent>();保存容器中的一些早期的事件; 2、obtainFreshBeanFactory();获取BeanFactory; 1)、refreshBeanFactory();刷新【创建】BeanFactory; 创建了一个this.beanFactory = new DefaultListableBeanFactory(); 设置id; 2)、getBeanFactory();返回刚才GenericApplicationContext创建的BeanFactory对象; 3)、将创建的BeanFactory【DefaultListableBeanFactory】返回; 3、prepareBeanFactory(beanFactory);BeanFactory的预准备工作(BeanFactory进行一些设置); 1)、设置BeanFactory的类加载器、支持表达式解析器... 2)、添加部分BeanPostProcessor【ApplicationContextAwareProcessor】 3)、设置忽略的自动装配的接口EnvironmentAware、EmbeddedValueResolverAware、xxx; 4)、注册可以解析的自动装配;我们能直接在任何组件中自动注入: BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext 5)、添加BeanPostProcessor【ApplicationListenerDetector】 6)、添加编译时的AspectJ; 7)、给BeanFactory中注册一些能用的组件; environment【ConfigurableEnvironment】、 systemProperties【Map<String, Object>】、 systemEnvironment【Map<String, Object>】 4、postProcessBeanFactory(beanFactory);BeanFactory准备工作完成后进行的后置处理工作; 1)、子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置 ======================以上是BeanFactory的创建及预准备工作================================== 5、invokeBeanFactoryPostProcessors(beanFactory);执行BeanFactoryPostProcessor的方法; BeanFactoryPostProcessor:BeanFactory的后置处理器。在BeanFactory标准初始化之后执行的; 两个接口:BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor 1)、执行BeanFactoryPostProcessor的方法; 先执行BeanDefinitionRegistryPostProcessor 1)、获取所有的BeanDefinitionRegistryPostProcessor; 2)、看先执行实现了PriorityOrdered优先级接口的BeanDefinitionRegistryPostProcessor、 postProcessor.postProcessBeanDefinitionRegistry(registry) 3)、在执行实现了Ordered顺序接口的BeanDefinitionRegistryPostProcessor; postProcessor.postProcessBeanDefinitionRegistry(registry) 4)、最后执行没有实现任何优先级或者是顺序接口的BeanDefinitionRegistryPostProcessors; postProcessor.postProcessBeanDefinitionRegistry(registry) 再执行BeanFactoryPostProcessor的方法 1)、获取所有的BeanFactoryPostProcessor 2)、看先执行实现了PriorityOrdered优先级接口的BeanFactoryPostProcessor、 postProcessor.postProcessBeanFactory() 3)、在执行实现了Ordered顺序接口的BeanFactoryPostProcessor; postProcessor.postProcessBeanFactory() 4)、最后执行没有实现任何优先级或者是顺序接口的BeanFactoryPostProcessor; postProcessor.postProcessBeanFactory() 6、registerBeanPostProcessors(beanFactory);注册BeanPostProcessor(Bean的后置处理器)【 intercept bean creation】 不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的 BeanPostProcessor、 DestructionAwareBeanPostProcessor、 InstantiationAwareBeanPostProcessor、 SmartInstantiationAwareBeanPostProcessor、 MergedBeanDefinitionPostProcessor【internalPostProcessors】、 1)、获取所有的 BeanPostProcessor;后置处理器都默认可以通过PriorityOrdered、Ordered接口来执行优先级 2)、先注册PriorityOrdered优先级接口的BeanPostProcessor; 把每一个BeanPostProcessor;添加到BeanFactory中 beanFactory.addBeanPostProcessor(postProcessor); 3)、再注册Ordered接口的 4)、最后注册没有实现任何优先级接口的 5)、最终注册MergedBeanDefinitionPostProcessor; 6)、注册一个ApplicationListenerDetector;来在Bean创建完成后检查是否是ApplicationListener,如果是 applicationContext.addApplicationListener((ApplicationListener<?>) bean); 7、initMessageSource();初始化MessageSource组件(做国际化功能;消息绑定,消息解析); 1)、获取BeanFactory 2)、看容器中是否有id为messageSource的,类型是MessageSource的组件 如果有赋值给messageSource,如果没有自己创建一个DelegatingMessageSource; MessageSource:取出国际化配置文件中的某个key的值;能按照区域信息获取; 3)、把创建好的MessageSource注册在容器中,以后获取国际化配置文件的值的时候,可以自动注入MessageSource; beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource); MessageSource.getMessage(String code, Object[] args, String defaultMessage, Locale locale); 8、initApplicationEventMulticaster();初始化事件派发器; 1)、获取BeanFactory 2)、从BeanFactory中获取applicationEventMulticaster的ApplicationEventMulticaster; 3)、如果上一步没有配置;创建一个SimpleApplicationEventMulticaster 4)、将创建的ApplicationEventMulticaster添加到BeanFactory中,以后其他组件直接自动注入 9、onRefresh();留给子容器(子类) 1、子类重写这个方法,在容器刷新的时候可以自定义逻辑; 10、registerListeners();给容器中将所有项目里面的ApplicationListener注册进来; 1、从容器中拿到所有的ApplicationListener 2、将每个监听器添加到事件派发器中; getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); 3、派发之前步骤产生的事件; 11、finishBeanFactoryInitialization(beanFactory);初始化所有剩下的单实例bean; 1、beanFactory.preInstantiateSingletons();初始化后剩下的单实例bean 1)、获取容器中的所有Bean,依次进行初始化和创建对象 2)、获取Bean的定义信息;RootBeanDefinition 3)、Bean不是抽象的,是单实例的,是懒加载; 1)、判断是否是FactoryBean;是否是实现FactoryBean接口的Bean; 2)、不是工厂Bean。利用getBean(beanName);创建对象 0、getBean(beanName); ioc.getBean(); 1、doGetBean(name, null, null, false); 2、先获取缓存中保存的单实例Bean。如果能获取到说明这个Bean之前被创建过(所有创建过的单实例Bean都会被缓存起来) private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);获取的 3、缓存中获取不到,开始Bean的创建对象流程; 4、标记当前bean已经被创建 5、获取Bean的定义信息; 6、【获取当前Bean依赖的其他Bean;如果有按照getBean()把依赖的Bean先创建出来;】 7、启动单实例Bean的创建流程; 1)、createBean(beanName, mbd, args); 2)、Object bean = resolveBeforeInstantiation(beanName, mbdToUse);让BeanPostProcessor先拦截返回代理对象; 【InstantiationAwareBeanPostProcessor】:提前执行; 先触发:postProcessBeforeInstantiation(); 如果有返回值:触发postProcessAfterInitialization(); 3)、如果前面的InstantiationAwareBeanPostProcessor没有返回代理对象;调用4 4)、Object beanInstance = doCreateBean(beanName, mbdToUse, args);创建Bean 1)、【创建Bean实例】;createBeanInstance(beanName, mbd, args); 利用工厂方法或者对象的构造器创建出Bean实例; 2)、applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); 调用MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition(mbd, beanType, beanName); 3)、【Bean属性赋值】populateBean(beanName, mbd, instanceWrapper); 赋值之前: 1)、拿到InstantiationAwareBeanPostProcessor后置处理器; postProcessAfterInstantiation(); 2)、拿到InstantiationAwareBeanPostProcessor后置处理器; postProcessPropertyValues(); =====赋值之前:=== 3)、应用Bean属性的值;为属性利用setter方法等进行赋值; applyPropertyValues(beanName, mbd, bw, pvs); 4)、【Bean初始化】initializeBean(beanName, exposedObject, mbd); 1)、【执行Aware接口方法】invokeAwareMethods(beanName, bean);执行xxxAware接口的方法 BeanNameAware\BeanClassLoaderAware\BeanFactoryAware 2)、【执行后置处理器初始化之前】applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); BeanPostProcessor.postProcessBeforeInitialization(); 3)、【执行初始化方法】invokeInitMethods(beanName, wrappedBean, mbd); 1)、是否是InitializingBean接口的实现;执行接口规定的初始化; 2)、是否自定义初始化方法; 4)、【执行后置处理器初始化之后】applyBeanPostProcessorsAfterInitialization BeanPostProcessor.postProcessAfterInitialization(); 5)、注册Bean的销毁方法; 5)、将创建的Bean添加到缓存中singletonObjects; ioc容器就是这些Map;很多的Map里面保存了单实例Bean,环境信息。。。。; 所有Bean都利用getBean创建完成以后; 检查所有的Bean是否是SmartInitializingSingleton接口的;如果是;就执行afterSingletonsInstantiated(); 12、finishRefresh();完成BeanFactory的初始化创建工作;IOC容器就创建完成; 1)、initLifecycleProcessor();初始化和生命周期有关的后置处理器;LifecycleProcessor 默认从容器中找是否有lifecycleProcessor的组件【LifecycleProcessor】;如果没有new DefaultLifecycleProcessor(); 加入到容器; 写一个LifecycleProcessor的实现类,可以在BeanFactory void onRefresh(); void onClose(); 2)、 getLifecycleProcessor().onRefresh(); 拿到前面定义的生命周期处理器(BeanFactory);回调onRefresh(); 3)、publishEvent(new ContextRefreshedEvent(this));发布容器刷新完成事件; 4)、liveBeansView.registerApplicationContext(this); ======总结=========== 1)、Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息; 1)、xml注册bean;<bean> 2)、注解注册Bean;@Service@Component@Bean、xxx 2)、Spring容器会合适的时机创建这些Bean 1)、用到这个bean的时候;利用getBean创建bean;创建好以后保存在容器中; 2)、统一创建剩下所有的bean的时候;finishBeanFactoryInitialization(); 3)、后置处理器;BeanPostProcessor 1)、每一个bean创建完成,都会使用各种后置处理器进行处理;来增强bean的功能; AutowiredAnnotationBeanPostProcessor:处理自动注入 AnnotationAwareAspectJAutoProxyCreator:来做AOP功能; xxx.... 增强的功能注解: AsyncAnnotationBeanPostProcessor .... 4)、事件驱动模型; ApplicationListener;事件监听; ApplicationEventMulticaster;事件派发:

26 servlet3.0

26.1 环境准备

1.创建maven工程

在这里插入图片描述
注意添加javax.servlet这个依赖,因为如果不加这个依赖,下面的servlet继承不了HttpServlet。

26.2 创建index.html及HelloServlet并测试

index.html

<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="hello">Hello</a> </body> </html>

HelloServlet

@WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); resp.getWriter().write("hello..."); } }

在注解上使用/hello。

请注意将html的文件创建在webcontent目录下面,而不是web-inf.

然后启动tomact。点击超链接
在这里插入图片描述
在这里插入图片描述

结果如上图所示。

小结:

  1. @WebFilter -------- 注册Filter
  2. @WebServlet ---- 注册Servlet
  3. @WebListener — 注册Listener的
  4. @WebInitParam — 配置servlet和filter初始化参数

26.3 Shared libraries(共享库)/ runtimes pluggability(运行时插件能力)

26.3.1 概述

我们好好看看Servlet 3.0标准规范文档中Shared libraries / runtimes pluggability这一小节,大概在该小节的第二段描述中,有句话说的是,container(即Servlet容器,比如Tomcat服务器之类的)在启动我们的应用的时候,它会来扫描jar包里面的ServletContainerInitializer的实现类。

哦豁,我们现在知道了,当Servlet容器启动我们的应用时,它会扫描我们当前应用中每一个jar里面的ServletContainerInitializer的实现类。那究竟是一个怎么扫描法呢?我们再好好看看该小节的第二段描述,它说,我们得提供ServletContainerInitializer的一个实现类,提供完这个实现类之后还不行,我们还必须得把它绑定在META-INF/services/目录下面的名字叫javax.servlet.ServletContainerInitializer的文件里面。

也就是说,必须将提供的实现类绑定在META-INF/services/javax.servlet.ServletContainerInitializer文件中,所谓的绑定就是在javax.servlet.ServletContainerInitializer文件里面写上ServletContainerInitializer实现类的全类名,也就是说,javax.servlet.ServletContainerInitializer文件中的内容就是咱们提供的ServletContainerInitializer实现类的全类名。

至此,我们才总算搞清楚了这个非常重要的机制,总结一下就是,Servlet容器在启动应用的时候,会扫描当前应用每一个jar包里面的META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的实现类,然后,再运行该实现类中的方法。

26.3.2 相关类创建

MyServletContainerInitializer继承ServletContainerInitializer

//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来; //传入感兴趣的类型; @HandlesTypes(value={HelloService.class}) public class MyServletContainerInitializer implements ServletContainerInitializer { /** * 应用启动的时候,会运行onStartup方法; * * Set<Class<?>> arg0:感兴趣的类型的所有子类型; * ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用一个ServletContext; * * 1)、使用ServletContext注册Web组件(Servlet、Filter、Listener) * 2)、使用编码的方式,在项目启动的时候给ServletContext里面添加组件; * 必须在项目启动的时候来添加; * 1)、ServletContainerInitializer得到的ServletContext; * 2)、ServletContextListener得到的ServletContext; */ @Override public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException { // TODO Auto-generated method stub System.out.println("感兴趣的类型:"); for (Class<?> claz : arg0) { System.out.println(claz); } } }

Servlet容器在启动应用的时候,会将@HandlesTypes注解里面指定的类型下面的子类,包括实现类或者子接口等,全部都给我们传递过来。

26.4 注册三大组件

26.4.1 概述

使用ServletContext注册web组件。这些web组件就是我们通常所说的web三大组件,也就是Servlet、Filter以及Listener。

为什么我们一定要掌握使用ServletContext来注册web组件呢?因为我们是一定会遇到这样场景的,如果是以注解的方式来注册web组件,那么前提是这些web组件是由我们自己来编写的,这样,我们才可以把注解加上。但是,如果项目中导入的是第三方jar包,它们里面是有一些组件的,比如在项目中导入了阿里巴巴的连接池里面的Filter,对于这些组件而言,如果项目中有web.xml文件,那么我们就可以将它们配置在该配置文件中了,但是,我们现在的项目中是没有web.xml文件的,所以我们就要利用ServletContext将它们给注册进来了。

在这里插入图片描述

26.4.2 测试

分别创建filter\servlet\listener。

public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub resp.getWriter().write("tomcat..."); } }
public class UserFilter implements Filter { // 销毁方法 @Override public void destroy() { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { // 过滤请求 System.out.println("UserFilter...doFilter方法..."); // 放行 arg2.doFilter(arg0, arg1); } // 初始化方法 @Override public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } }
/** * ServletContextListener的作用:监听项目的启动和停止 * @author liayun * */ public class UserListener implements ServletContextListener { // 这个方法是来监听ServletContext销毁的,也就是说,我们这个项目的停止 @Override public void contextDestroyed(ServletContextEvent arg0) { // TODO Auto-generated method stub System.out.println("UserListener...contextDestroyed..."); } // 这个方法是来监听ServletContext启动初始化的 @Override public void contextInitialized(ServletContextEvent arg0) { // TODO Auto-generated method stub System.out.println("UserListener...contextInitialized..."); } }

在类中进行注册组件测试

//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来; //传入感兴趣的类型; @HandlesTypes(value={HelloService.class}) public class MyServletContainerInitializer implements ServletContainerInitializer { /** * 应用启动的时候,会运行onStartup方法; * * Set<Class<?>> arg0:感兴趣的类型的所有子类型; * ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用一个ServletContext; * * 1)、使用ServletContext注册Web组件(Servlet、Filter、Listener) * 2)、使用编码的方式,在项目启动的时候给ServletContext里面添加组件; * 必须在项目启动的时候来添加; * 1)、ServletContainerInitializer得到的ServletContext; * 2)、ServletContextListener得到的ServletContext; */ @Override public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException { // TODO Auto-generated method stub System.out.println("感兴趣的类型:"); for (Class<?> claz : arg0) { System.out.println(claz); } //注册组件 ServletRegistration ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet()); //配置servlet的映射信息 servlet.addMapping("/user"); //注册Listener sc.addListener(UserListener.class); //注册Filter FilterRegistration FilterRegistration.Dynamic filter = sc.addFilter("userFilter", UserFilter.class); //配置Filter的映射信息 filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); } }

注意点:

  1. 调用ServletContext对象的addServlet方法(即注册Servlet)和addFilter方法(即注册Filter),都会返回一个Dynamic对象,只不过一个是ServletRegistration里面的Dynamic,一个是FilterRegistration里面的Dynamic。

关于addMappingForUrlPatterns方法:

  1. addMappingForUrlPatterns方法中传入的第一个参数还是蛮奇怪的,居然是EnumSet.of(DispatcherType.REQUEST),该参数表示的是Filter拦截的请求类型,即通过什么方式过来的请求,Filter会进行拦截。我们不妨点进DispatcherType枚举的源码里面去看一看,如下图所示,可以看到好多的请求类型,不过常用的就应该是FORWARD和REQUEST它俩。
    在这里插入图片描述
  2. 现在addMappingForUrlPatterns方法中传入的第一个参数是EnumSet.of(DispatcherType.REQUEST),表明我们写的UserFilter会拦截通过request方式发送过来的请求。
    该方法中的第二个参数(即isMatchAfter)我们直接传入true就行,第三个参数(即urlPatterns)就是Filter要拦截的路径,目前我们传入的是/*,即拦截所有请求。

以上就是我们以编码的方式向ServletContext对象中注册web中的三大组件。

26.4.3 总结

我们可以通过编码的方式在项目启动的时候,给ServletContext(即当前项目)里面来注册组件。当然,并不是说,你只要拿到了ServletContext对象就能注册组件了,因为必须是在项目启动的时候,才能注册组件。

而且,在项目启动的时候,我们可以有两处来使用ServletContext对象注册组件。

第一处就是利用基于运行时插件的ServletContainerInitializer机制得到ServletContext对象,然后再往其里面注册组件。本讲通篇所讲述的就是在这一处使用ServletContext对象来注册组件。

第二处,你可能想不到,我们上面不是编写过一个监听器(即UserListener)吗?它是来监听项目的启动和停止的,在监听项目启动的方法中,传入了一个ServletContextEvent对象,即事件对象,我们就可以通过该事件对象的getServletContext方法拿到ServletContext对象,拿到之后,是不是就可以往它里面注册组件了啊?

/** * ServletContextListener的作用:监听项目的启动和停止 * @author liayun * */ public class UserListener implements ServletContextListener { // 这个方法是来监听ServletContext销毁的,也就是说,我们这个项目的停止 @Override public void contextDestroyed(ServletContextEvent arg0) { // TODO Auto-generated method stub System.out.println("UserListener...contextDestroyed..."); } // 这个方法是来监听ServletContext启动初始化的 @Override public void contextInitialized(ServletContextEvent arg0) { // TODO Auto-generated method stub ServletContext servletContext = arg0.getServletContext(); System.out.println("UserListener...contextInitialized..."); } }

26.5 Servlet 3.0与Spring MVC的整合分析

官方文档如下:

//我们可以编写一个类来实现WebApplicationInitializer接口,当然了,你也可以编写一个类来实现ServletContainerInitializer接口 public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) { // Load Spring web application configuration //然后,我们来创建一个AnnotationConfigWebApplicationContext对象,它应该代表的是web的IOC容器 AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); // 加载我们的配置类 context.register(AppConfig.class); // Create and register the DispatcherServlet // 在容器启动的时候,我们自己来创建一个DispatcherServlet对象,并将其注册在ServletContext中 DispatcherServlet servlet = new DispatcherServlet(context); ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet); registration.setLoadOnStartup(1); // 这儿是来配置DispatcherServlet的映射信息的 registration.addMapping("/app/*"); } }

Servlet容器在启动我们Spring应用之后,会传入一个我们感兴趣的类型的集合,然后在onStartup方法中拿到之后就会来挨个遍历,如果遍历出来的我们感兴趣的类型不是接口,也不是抽象类,但是WebApplicationInitializer接口旗下的,那么就会创建该类型的一个实例,并将其存储到名为initializers的LinkedList集合中。

也可以这样说,我们Spring的应用一启动就会加载感兴趣的WebApplicationInitializer接口旗下的所有组件,并且为这些WebApplicationInitializer组件创建对象,当然前提是这些组件即不是接口,也不是抽象类。

1、web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer 2、加载这个文件指定的类SpringServletContainerInitializer 3、spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件; 4、并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类) 1)、AbstractContextLoaderInitializer:创建根容器;createRootApplicationContext(); 2)、AbstractDispatcherServletInitializer: 创建一个web的ioc容器;createServletApplicationContext(); 创建了DispatcherServlet;createDispatcherServlet(); 将创建的DispatcherServlet添加到ServletContext中; getServletMappings(); 3)、AbstractAnnotationConfigDispatcherServletInitializer:注解方式配置的DispatcherServlet初始化器 创建根容器:createRootApplicationContext() getRootConfigClasses();传入一个配置类 创建web的ioc容器: createServletApplicationContext(); 获取配置类;getServletConfigClasses(); 总结: 以注解方式来启动SpringMVC;继承AbstractAnnotationConfigDispatcherServletInitializer; 实现抽象方法指定DispatcherServlet的配置信息;

未完待续

27 定制与接管 Spring MVC

过时?
定制SpringMVC;
1)、@EnableWebMvc:开启SpringMVC定制配置功能;
mvc:annotation-driven/;

2)、配置组件(视图解析器、视图映射、静态资源映射、拦截器。。。)
extends WebMvcConfigurerAdapter

28 异步处理

WebServlet注解有一个asyncSupported属性,其值默认为false,我们只须将其值设置为true,HelloAsyncServlet就能支持异步处理了。
在这里插入图片描述
异步处理步骤:

// @WebServlet注解表明该Servlet应该处理哪个请求 @WebServlet(value="/async", asyncSupported=true) public class HelloAsyncServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 先来让该Servlet支持异步处理,即asyncSupported=true // 2. 开启异步模式 // 我们可以来打印一下主线程究竟是谁? System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis()); AsyncContext startAsync = req.startAsync(); // 3. 调用业务逻辑,进行异步处理,这儿是开始异步处理 startAsync.start(new Runnable() { @Override public void run() { // TODO Auto-generated method stub try { System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis()); sayHello(); startAsync.complete(); /* * 通过下面这种方式来获取响应对象是不可行的哟!否则,会报如下异常: * java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false) */ // 获取到异步上下文 //AsyncContext asyncContext = req.getAsyncContext(); // ServletResponse response = asyncContext.getResponse(); // 4. 获取响应 ServletResponse response = startAsync.getResponse(); // 然后,我们还是利用这个响应往客户端来写数据 response.getWriter().write("hello async..."); System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis()); } public void sayHello() throws Exception { // 我们可以来打印一下究竟是哪些线程在工作 System.out.println(Thread.currentThread() + " processing..."); Thread.sleep(3000); // 睡上3秒 } }

在这里插入图片描述

29 Spring MVC中的异步请求处理(返回Callable)

/** * 1、控制器返回Callable * 2、Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行 * 3、DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态; * 4、Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理; * 5、根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。 * * preHandle.../springmvc-annotation/async01 主线程开始...Thread[http-bio-8081-exec-3,5,main]==>1513932494700 主线程结束...Thread[http-bio-8081-exec-3,5,main]==>1513932494700 =========DispatcherServlet及所有的Filter退出线程============================ ================等待Callable执行========== 副线程开始...Thread[MvcAsync1,5,main]==>1513932494707 副线程开始...Thread[MvcAsync1,5,main]==>1513932496708 ================Callable执行完成========== ================再次收到之前重发过来的请求======== preHandle.../springmvc-annotation/async01 postHandle...(Callable的之前的返回值就是目标方法的返回值) afterCompletion... 异步的拦截器: 1)、原生API的AsyncListener 2)、SpringMVC:实现AsyncHandlerInterceptor;
@Controller public class AsyncController { @ResponseBody @RequestMapping("/async01") public Callable<String> async01(){ System.out.println("主线程开始。。。"+Thread.currentThread()+"====>"+System.currentTimeMillis()); Callable<String> callable = new Callable<String>(){ @Override public String call() throws Exception { System.out.println("副线程开始..."+Thread.currentThread()+"====>"+System.currentTimeMillis()); Thread.sleep(2000); System.out.println("副线程结束..."+Thread.currentThread()+"===>"+System.currentTimeMillis()); return "Callable<String> async01()"; } }; System.out.println("主线程结束..."+Thread.currentThread()+"===>"+System.currentTimeMillis()); return callable; } }

测试结果如下图所示:
在这里插入图片描述

30 pring MVC中的异步请求处理(返回DeferredResult)

在这里插入图片描述

@Controller public class AsyncController { @ResponseBody @RequestMapping("/createOrder") public DeferredResult<Object> createOrder(){ /** * 第一个参数(timeout): 超出时间,限定必须在该时间内执行完。如果超出时间限制,那么就会返回一段错误的提示信息 * 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息 */ DeferredResult<Object> deferredResult = new DeferredResult<>((long) 3000, "create fail..."); DederredResultQueue.save(deferredResult); return deferredResult; } @ResponseBody @RequestMapping("/async01") public Callable<String> async01(){ System.out.println("主线程开始。。。"+Thread.currentThread()+"====>"+System.currentTimeMillis()); Callable<String> callable = new Callable<String>(){ @Override public String call() throws Exception { System.out.println("副线程开始..."+Thread.currentThread()+"====>"+System.currentTimeMillis()); Thread.sleep(2000); System.out.println("副线程结束..."+Thread.currentThread()+"===>"+System.currentTimeMillis()); return "Callable<String> async01()"; } }; System.out.println("主线程结束..."+Thread.currentThread()+"===>"+System.currentTimeMillis()); return callable; } public String create(){ //模拟订单创建 String order = UUID.randomUUID().toString(); DeferredResult<Object> deferredResult = DederredResultQueue.get(); deferredResult.setResult(order); return "success ====> " +order; }
public class DederredResultQueue { //DeferredResult对象临时保存 的地方 private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedDeque<DeferredResult<Object>>(); //临时保存DeferredResult对象的方法 public static void save(DeferredResult<Object> deferredResult){ queue.add(deferredResult); } //获取DeffedResult对象的方法 public static DeferredResult<Object> get(){ /** poll():检索并且移除,移除的是队列头部的元素 */ return queue.poll(); } }

总结

这部分spring注解开发驱动视频教程中源码部分需要一定工作经验,只能等工作之后再来看,提升自己。前期部分有些知识已经过时。目前就这样吧,等以后完善。


__EOF__

本文作者hxld
本文链接https://www.cnblogs.com/hxld/p/16829438.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   hxld  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示