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();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值