Spring注解驱动开发(一)

注:此笔记为尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)学习笔记,并同时参考[https://blog.csdn.net/xjhqre/article/details/123264069]博主文章,其中包含个人的笔记和理解,仅做学习笔记之用。

Spring注解驱动开发(一)_第1张图片Spring注解驱动开发(一)_第2张图片

整个专栏分成了三个大的部分,分别是:容器、扩展原理以及Web

1、学习安排

1.1、容器

容器作为整个专栏的第一大部分,内容包括:

  • AnnotationConfigApplicationContext
  • 组件添加
  • 组件赋值
  • 组件注入
  • AOP
  • 声明式事务

1.2、扩展原理

扩展原理作为整个专栏的第二大部分,内容包括:

  • BeanFactoryPostProcessor
  • BeanDefinitionRegistryPostProcessor
  • ApplicationListener
  • Spring容器创建过程

1.3、Web

Web作为整个专栏的第三大部分,内容包括:

  • servlet3.0
  • 异步请求

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

2、配置文件开发

2.1、导入Spring-context依赖包


<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-contextartifactId>
    <version>5.3.13version>
dependency>

2.2、编写Spring配置文件

在 resources 目录下创建 application.xml 文件


<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">

beans>

2.3、编写Person类

public class Person {
    
    private String name;
    private Integer age;
	
    // 省略构造方法,get/set方法,toString方法
}

2.4、在Spring配置文件中创建Bean

<bean id="person" class="com.kdz.pojo.Person">
    <property name="name" value="kdz"/>
    <property name="age" value="18"/>
bean>

2.5、编写测试类

public class SpringTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);
    }
}

3、注解开发简单案例

3.1、创建配置类

在方法上面加上@Bean注解后,Spring会以方法返回类型作为组件的类型,方法名作为组件的 id

当向@Bean中添加参数时,默认添加的第一个参数是value,强制给组件赋值id

Spring注解驱动开发(一)_第3张图片

// 配置类等同于xml配置文件
@Configuration // 告诉Spring这是一个配置类
public class MainConfig {

    // 给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
    @Bean("person")
    public Person person01(){
        return new Person("lisi", 20);
    }
}

3.2、测试代码

public static void main(String[] args) {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
    Person bean = applicationContext.getBean(Person.class);
    System.out.println(bean);
}

Spring注解驱动开发(一)_第4张图片

4、@ComponentScan包扫描

只要标注了@Controller、@Service、@Repository、@Component的,都会被扫描加入到容器里

注意:配置类自身也会被扫描到容器中,如果存在多个配置类,则多个配置类里的所有bean对象都会被扫描进容器中

4.1、配置文件包扫描

Spring注解驱动开发(一)_第5张图片

<context:component-scan base-package="com.kdz"/>

4.2、注解扫描

在配置类上添加注解@ComponentScan

Spring注解驱动开发(一)_第6张图片

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig {
    // ...
}

在这里插入图片描述

4.3、排除过滤扫描

注解排除excludeFilters的返回类型为Filter[]

Filter 的排除类型 FilterType 有一下几种:

ANNOTATION:按注解排除,常用
ASSIGNABLE_TYPE:按类型排除,常用
ASPECTJ:按AspectJ 类型模式表达式排除,不常用
REGEX:按正则表达式排除
CUSTOM:自定义排除

Spring注解驱动开发(一)_第7张图片

4.4、指定扫描

只包含过滤规则生效,需要禁用默认的规则

扫描的时候只需要包含哪些组件,编写方式和excludeFilters一样

注意要关闭默认的扫描过滤器

Spring注解驱动开发(一)_第8张图片

Spring注解驱动开发(一)_第9张图片

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
}, useDefaultFilters = false)
public class MainConfig {
    // ...
}

在 jdk8 之后可以写多个@ComponentScans,写法如下:

@Configuration // 告诉Spring这是一个配置类
@ComponentScans(
        value = {
                @ComponentScan(value = "com.kdz", includeFilters = {
                        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
                }, useDefaultFilters = false)
        }
)
public class MainConfig {
    // ...
}

4.5、按类型扫描

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookController.class})
}, useDefaultFilters = false)
public class MainConfig {
    // ...
}

4.6、自定义扫描

4.6.1、编写TypeFilter的实现类
public class MyTypeFilter implements TypeFilter {

    /**
     * @param metadataReader 读取到当前正在扫描类的信息
     * @param 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);
        return className.contains("er");
    }
}

Spring注解驱动开发(一)_第10张图片

4.6.2、修改配置类
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz", includeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
}, useDefaultFilters = false)
public class MainConfig {
    // ...
}

5、@scope注解

5.1、默认单实例

5.1.1、编写配置类

bean对象默认是单实例的

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig2 {

    @Bean("person")
    public Person person() {
        return new Person("张三", 25);
    }
}
5.1.2、编写测试

Spring注解驱动开发(一)_第11张图片

@Test
public void test2() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    Person bean = applicationContext.getBean(Person.class);
    Person bean2 = applicationContext.getBean(Person.class);
    System.out.println(bean.hashCode());
    System.out.println(bean2.hashCode());
}

Spring注解驱动开发(一)_第12张图片

5.2、@scope参数

ConfigurableBeanFactory.SCOPE_PROTOTYPE,
ConfigurableBeanFactory.SCOPE_SINGLETON,
org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST,
org.springframework.web.context.WebApplicationContext.SCOPE_SESSION, value
后两个需要导入 Spring web 的jar包,且基本不用,只讨论前两个

Spring注解驱动开发(一)_第13张图片

Spring注解驱动开发(一)_第14张图片

ConfigurableBeanFactory中有两个常量

// 单实例,默认值,在IOC容器启动时就会调用方法创建对象,以后每次获取直接从容器中拿(map.get())
String SCOPE_SINGLETON = "singleton";
// 多实例,在使用getBean()获取bean对象时才会创建
String SCOPE_PROTOTYPE = "prototype";

Spring注解驱动开发(一)_第15张图片

WebApplicationContext中的两个常量

// 同一次请求创建一个实例
String SCOPE_REQUEST = "request";
// 同一个session创建一个实例
String SCOPE_SESSION = "session";

5.3、测试多实例

5.3.1、修改配置类
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig2 {
    
    @Bean("person")
    @Scope("prototype")
    public Person person() {
        return new Person("张三", 25);
    }
}
5.3.2、测试代码

Spring注解驱动开发(一)_第16张图片

@Test
public void test2() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    Person bean = applicationContext.getBean(Person.class);
    Person bean2 = applicationContext.getBean(Person.class);
    System.out.println(bean.hashCode());
    System.out.println(bean2.hashCode());
}

两个 bean 的哈希值不同

1.bean默认是单实例的,只在创建容器时创建一次

单实例,默认值,在IOC容器启动时就会调用方法创建对象,以后每次获取直接从容器中拿(map.get())

Spring注解驱动开发(一)_第17张图片

Spring注解驱动开发(一)_第18张图片

2.bean多实例时:ioc容器创建的时候,先不创建实例,当获取实例的时候,再依次创建实例,所以每次创建的实例都不一样

Spring注解驱动开发(一)_第19张图片

Spring注解驱动开发(一)_第20张图片

5.4、懒加载

针对单实例设置,因为**单实例是在容器启动时创建对象**

懒加载就是不让单实例在容器启动时创建,在第一次获取Bean时创建对象并初始化

只需要在方法上添加注解@Lazy

Spring注解驱动开发(一)_第21张图片

Spring注解驱动开发(一)_第22张图片

第一次获取bean实例的时候创建
Spring注解驱动开发(一)_第23张图片

Spring注解驱动开发(一)_第24张图片

Spring注解驱动开发(一)_第25张图片

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig2 {

    @Bean("person")
    @Lazy
    public Person person() {
        return new Person("张三", 25);
    }

}

6、@Conditional注解(重点)

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

6.0、源码分析

@Conditional源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

    // 传入一个Condition数组
    Class<? extends Condition>[] value();
}

Spring注解驱动开发(一)_第26张图片

Condition接口源码

@FunctionalInterface
public interface Condition {

    /**
     * ConditionContext:判断条件能使用的上下文环境
     * AnnotatedTypeMetadata:注释信息
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

Spring注解驱动开发(一)_第27张图片

ConditionContext:判断条件能使用的上下文环境

AnnotatedTypeMetadata:注释信息

6.1、业务场景

如果是linux系统则注册linus,如果是windows系统则注册bill Gates

Spring注解驱动开发(一)_第28张图片

6.2、编写WindowsCondition

编写windows条件,需要实现接口Condition,重写matches方法

public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 判断是否是Windows系统
        // 1. 获取到IOC使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 2. 获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        // 3. 获取运行环境
        Environment environment = context.getEnvironment();
        // 4. 获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        // 容器中是否包含person,判断容器中的bean注册情况,也可以给容器中注册bean
        boolean person = registry.containsBeanDefinition("person");

        // 运行环境是否是Windows
        String property = environment.getProperty("os.name");
        assert property != null;
        return property.contains("Windows");
    }
}

6.3、编写LinuxCondition

public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        assert property != null;
        return property.contains("linux");
    }
}

6.4、编写配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig3 {

    @Bean("bill")
    @Conditional({WindowsCondition.class})
    public Person person() {
        return new Person("bill gates", 62);
    }

    @Bean("linus")
    @Conditional({LinuxCondition.class})
    public Person person2() {
        return new Person("linus", 48);
    }
}

6.5、编写测试

Spring注解驱动开发(一)_第29张图片

@Test
public void test3() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
    Map<String, Person> beansOfType = applicationContext.getBeansOfType(Person.class);
    System.out.println(beansOfType);
}

Spring注解驱动开发(一)_第30张图片

未测试

改变运行时的变量

Spring注解驱动开发(一)_第31张图片

Spring注解驱动开发(一)_第32张图片

Spring注解驱动开发(一)_第33张图片

当@Conditional配置在类上时,表示只有满足条件时,这个配置类配置的所有bean才会生效

不仅可以放在方法上,还可以放在类上

Spring注解驱动开发(一)_第34张图片

7、组件注册方法

1.包扫描 + 组件标注注解(@Controller、@Service、@Repository、@Component),这种方式只能注册自己写的类
2.@Bean,可以注册第三方包的类
3.@Import:快速给容器中导入一个组件,id默认是全类名
ImportSelector:一个接口,返回需要导入的组件的全类名数组
ImportBeanDefinitionRegistrar:一个接口,手动注册bean到容器中
4.使用Spring提供的FactoryBean(工厂Bean)

7.1、@Import(重点)

Spring注解驱动开发(一)_第35张图片

7.1.1、编写配置类
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
@Import({Color.class, Red.class}) // 导入组件,id默认是组件的全类名
public class MainConfig3 {}

Spring注解驱动开发(一)_第36张图片

7.1.2、编写测试
@Test
public void test4() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}

Spring注解驱动开发(一)_第37张图片

7.2、ImportSelector

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

Spring注解驱动开发(一)_第38张图片

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
@Import({Color.class, Red.class, MyImportSelector.class}) // 导入组件,id默认是组件的全类名
public class MainConfig3 {}

Spring注解驱动开发(一)_第39张图片

7.2.3、测试
@Test
public void test4() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}

在这里插入图片描述

7.3、ImportBeanDefinitionRegistrar

Spring注解驱动开发(一)_第40张图片

7.3.1、编写MyImportBeanDefinitionRegistrar
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * AnnotationMetadata:当前类的注解信息
     * BeanDefinitionRegistry:注册类
     * 把所有需要添加到容器中的bean,调用BeanDefinitionRegistry.registerBeanDefinition手动注册
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 查询容器中是否存在red组件
        boolean red = registry.containsBeanDefinition("com.kdz.pojo.Red");
        if (red) {
            // 指定bean的定义信息(bean的类型,bean的scope)
            BeanDefinition beanDefinition = new RootBeanDefinition(Purple.class);
            // 第一个参数指定bean的id
            registry.registerBeanDefinition("purple", beanDefinition);
        }
    }
}
7.3.2、修改配置类
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig3 {}
7.3.3、测试
@Test
public void test4() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}

Spring注解驱动开发(一)_第41张图片

Spring注解驱动开发(一)_第42张图片

7.4、FactoryBean

FactoryBean是一个接口,我们需要自己实现

Spring注解驱动开发(一)_第43张图片

源码分析(自己点源码)

Spring注解驱动开发(一)_第44张图片

Spring注解驱动开发(一)_第45张图片

需要获取FactoryBean本身,需要+ &

Spring注解驱动开发(一)_第46张图片

Spring注解驱动开发(一)_第47张图片

7.4.1、编写ColorFactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {

    // 返回一个Color对象,这个对象会添加到容器中
    @Override
    public Color getObject() throws Exception {
        return new Color();
    }

    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

    // 返回是否为单例,如果为false,则每次创建时调用getObject()方法
    @Override
    public boolean isSingleton() {
        return true;
    }
}
7.4.2、编写配置类
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.kdz")
public class MainConfig4 {

    @Bean
    public ColorFactoryBean colorFactoryBean() {
        return new ColorFactoryBean();
    }
}
7.4.3、测试
@Test
public void test5() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4.class);
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    // 工厂bean获取的是调用getObject创建的对象
    Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
    System.out.println(colorFactoryBean.getClass());

    // 如果想要拿到colorFactoryBean对象,则需要在传入的id前加一个&标识
    Object colorFactoryBean2 = applicationContext.getBean("&colorFactoryBean");
    System.out.println(colorFactoryBean2.getClass());
}

Spring注解驱动开发(一)_第48张图片

7.5、FactoryBean和BeanFactory的区别

BeanFactory是接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范

FactoryBean也是接口为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式

区别:BeanFactory是个Factory,也就是IOC容器或对象工厂FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

1、BeanFactory的详细介绍

BeanFactory,以Factory结尾,表示它是一个工厂类(接口), 它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中*XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。

都是附加了某种功能的实现。 它为其他具体的IOC容器提供了最基本的规范,例如DefaultListableBeanFactory,XmlBeanFactory,ApplicationContext 等具体的容器都是实现了BeanFactory,再在其基础之上附加了其他的功能。

BeanFactory和ApplicationContext就是spring框架的两个IOC容器现在一般使用ApplicationnContext,其不但包含了BeanFactory的作用,同时还进行更多的扩展

BeanFacotry是spring中比较原始的Factory。如XMLBeanFactory就是一种典型的BeanFactory。
原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。ApplicationContext接口,它由BeanFactory接口派生而来,

ApplicationContext包含BeanFactory的所有功能,通常建议比BeanFactory优先

ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,ApplicationContext包还提供了以下的功能:
• MessageSource, 提供国际化的消息访问
• 资源访问,如URL和文件
• 事件传播
• 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层;

在不使用spring框架之前,我们的service层中要使用dao层的对象,不得不在service层中new一个对象。存在的问题:层与层之间的依赖。

service层要用dao层对象需要配置到xml配置文件中,至于对象是怎么创建的,关系是怎么组合的都交给了spring框架去实现。

2、FactoryBean的详细介绍

一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为**FactoryBean**的形式

以Bean结尾,表示它是一个Bean,不同于普通Bean的是:它是实现了FactoryBean接口的Bean根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取

例如自己实现一个FactoryBean,功能:用来代理一个对象,对该对象的所有方法做一个拦截,在调用前后都输出一行LOG,模仿ProxyFactoryBean的功能

你可能感兴趣的:(Spring,spring,java,后端)