Spring常用注解


组件添加

@Configuration+@Bean

xml文件的方式:
  1. 实体类

    /**
     * @Description 实体类
     * @Date 2021/12/17 15:50
     * @Created Prannt
     */
    @Data
    public class Person {
        private String name;
        private Integer age;
        private String nickName;
    }
  2. bean1.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"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
          http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
          http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
        <context:component-scan base-package="com.pan.entity" use-default-filters="false"></context:component-scan>
        <bean id="person" class="com.pan.entity.Person">
            <property name="age" value="18"></property>
            <property name="name" value="zhangsan"></property>
        </bean>
    </beans>
  3. 测试类

    public class MainTest {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            Person bean = (Person) context.getBean("person");
            System.out.println(bean);
        }
    }
  4. 输出

    Person(name=zhangsan, age=18, nickName=null)
注解的方式:
  1. 实体类

    @Data
    @AllArgsConstructor
    public class Person {
        private String name;
        private Integer age;
        private String nickName;
    }
  2. 配置类

    // 配置类 == 配置文件
    @Configuration  // 告诉Spring这是一个配置类
    public class MainConfig {
    
        //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id(就是bean的名字),在这里就是person
        @Bean(name = "person")
        public Person person(){
            return new Person("李四",20,"二狗");
        }
    }
  3. 测试类

    public class MainTest {
        public static void main(String[] args) {
            ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
            Person bean = context.getBean("person",Person.class);
            System.out.println(bean);
        }
    }
  4. 输出

    Person(name=李四, age=20, nickName=二狗)

@ComponentScans

  1. 实体类(同上)

  2. 指定包扫描规则

    public class MyTypeFilter implements TypeFilter {
        /**
         * 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);
            if(className.contains("er")){
                return true;
            }
            return false;
        }
    }
  3. 配置类

    // 配置类 == 配置文件
    @Configuration  // 告诉Spring这是一个配置类
    @ComponentScans(value =
                            {@ComponentScan(value="com.pan",includeFilters = {
                                    // @Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
                                    // @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
                                    @ComponentScan.Filter(type= FilterType.CUSTOM,classes={MyTypeFilter.class})},
                                    useDefaultFilters = false)})
    // @ComponentScan  value:指定要扫描的包
    // excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
    // includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
    // FilterType.ANNOTATION:按照注解
    // FilterType.ASSIGNABLE_TYPE:按照给定的类型;
    // FilterType.ASPECTJ:使用ASPECTJ表达式
    // FilterType.REGEX:使用正则指定
    // FilterType.CUSTOM:使用自定义规则
    public class MainConfig2 {
        //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
        @Bean("person")
        public Person person(){
            return new Person("王五", 20,"三狗");
        }
    }
  4. 测试类

    @Test
    public void test2(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
        Person bean = context.getBean("person",Person.class);
        System.out.println(bean);
    }
  5. 输出

    --->com.pan.entity.Person
    Person(name=王五, age=20, nickName=三狗)

@Scope

  1. 实体类(同上)

  2. 配置类

    @Configuration
    public class MainConfig3 {
        /**
         * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE   任何环境都可以使用
         * @see ConfigurableBeanFactory#SCOPE_SINGLETON   任何环境都可以使用
         * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST  request    只能在web容器里用
         * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION     sesssion   只能在web容器里用
         *
         * @Scope:调整作用域
         * prototype:多实例的:IOC容器启动并不会去调用方法创建对象放在容器中。
         *                     每次获取的时候才会调用方法创建对象;
         * singleton:单实例的(默认值):IOC容器启动会调用方法创建对象放到 IOC容器中。
         *             以后每次获取就是直接从容器(map.get())中拿,
         * request:同一次请求创建一个实例
         * session:同一个session创建一个实例
         *
         * 默认是单实例的
         *
         */
        @Scope("prototype")
        @Lazy
        @Bean("person")
        public Person person(){
            System.out.println("给容器中添加Person....");
            return new Person("王王", 25,"汪汪叫");
        }
    }
  3. 测试类

    @Test
    public void test3(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
        Person bean = context.getBean("person",Person.class);
        System.out.println(bean);
    }
  4. 输出

    给容器中添加Person....
    Person(name=王王, age=25, nickName=汪汪叫)

@Lazy

@Configuration
public class MainConfig4 {
   /**
    * 懒加载:
    *        单实例bean:默认在容器启动的时候创建对象;
    *        懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化。
    */
   @Lazy
   @Bean("person")
   public Person person(){
      System.out.println("给容器中添加Person....");
      return new Person("乔六", 25,"乔乔");
   }
}

@Conditional

需求:根据当前操作系统注入Person实例

  1. 实体类(同上)

  2. 判断是否是Linux系统

    // 判断是否linux系统
    public class LinuxCondition implements Condition {
        /**
         * ConditionContext:判断条件能使用的上下文(环境)
         * AnnotatedTypeMetadata:注释信息
         */
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            // TODO是否linux系统
            // 1 能获取到ioc使用的beanfactory
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            // 2 获取类加载器
            ClassLoader classLoader = context.getClassLoader();
            // 3 获取当前环境信息
            Environment environment = context.getEnvironment();
            // 4 获取到bean定义的注册类
            BeanDefinitionRegistry registry = context.getRegistry();
    
            String property = environment.getProperty("os.name");
    
            // 可以判断容器中的bean注册情况,也可以给容器中注册bean
            boolean definition = registry.containsBeanDefinition("person");
            if (property.contains("linux")) {
                return true;
            }
            return false;
        }
    }
  3. 判断是否是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;
        }
    }
  4. 配置类

    // 类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效;
    @Conditional({WindowsCondition.class})
    @Configuration
    public class MainConfig5 {
        /**
         * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册 bean
         *
         * 如果系统是 windows,给容器中注册("bill")
         * 如果是 linux系统,给容器中注册("linus")
         */
        @Bean("bill")
        public Person person01(){
            return new Person("Bill Gates",62,"Bill");
        }
    
        @Conditional(LinuxCondition.class)
        @Bean("linus")
        public Person person02(){
            return new Person("linus", 48,"linux");
        }
    }
  5. 测试类

    @Test
    public void test4(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
        Map<String,Person> map = context.getBeansOfType(Person.class);
        System.out.println(map);
    }
  6. 输出

    {bill=Person(name=Bill Gates, age=62, nickName=Bill)}
    // 以上输出也能证明:本机是Windows系统

@Import

@Import注解的作用是给容器中导入组件。可以通过Spring的xm配置方式,可以通过注解,如@Component等,也可以通过java配置类的方式给容器中导入注解,现在绍介绍另一个注解,其作用也是给容器中导入组件。

@Configuration
// 将这两个类导入到容器中
@Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
// @Import导入组件,id默认是组件的全类名
public class MainConfig5 {
    /**
     * 给容器中注册组件;
     * 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[只能注册自己写的类]
     * 2)、@Bean[导入的第三方包里面的组件]
     * 3)、@Import[快速给容器中导入一个组件]
     *        1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
     *        2)、ImportSelector:返回需要导入的组件的全类名数组;
     *        3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中
     */
}
// 自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
    // 返回值:导入到容器中的组件全类名
    // AnnotationMetadata:@Import引入MyImportSelector的类的所有注解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // importingClassMetadata.get
        // 方法不要返回null值,不然会报错
        return new String[]{"com.pan.entity.Blue","com.pan.entity.Yellow"};
    }
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * AnnotationMetadata:当前类的注解信息
     * BeanDefinitionRegistry:BeanDefinition注册类;
     *        把所有需要添加到容器中的bean;调用
     *        BeanDefinitionRegistry.registerBeanDefinition手工注册进来
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        boolean definition = registry.containsBeanDefinition("com.pan.entity.Red");
        boolean definition2 = registry.containsBeanDefinition("com.pan.entity.Blue");
        if(definition && definition2){
            // 指定Bean定义信息;(Bean的类型,Bean。。。)
            RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
            // 注册一个Bean,指定bean名
            registry.registerBeanDefinition("rainBow", beanDefinition);
        }
    }
}

FactoryBean

public class Car {
}
public class Color {
    private Car car;
    public Car getCar() {
        return car;
    }
    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Color [car=" + car + "]";
    }
}
// 创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {
    // 返回一个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 false;
    }
}
@Configuration
public class MainConfig2 {
    /**
     * 给容器中注册组件;
     * 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类]
     * 2)、@Bean[导入的第三方包里面的组件]
     * 3)、@Import[快速给容器中导入一个组件]
     *        1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
     *        2)、ImportSelector:返回需要导入的组件的全类名数组;
     *        3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中
     * 4)、使用Spring提供的 FactoryBean(工厂Bean);
     *        1)、默认获取到的是工厂bean调用getObject创建的对象
     *        2)、要获取工厂Bean本身,我们需要给id前面加一个&
     *			&colorFactoryBean
     *
     * 虽然这里装配的是ColorFactoryBean,但实际上beand的类型是Color
     */
    @Bean
    public ColorFactoryBean colorFactoryBean(){
        return new ColorFactoryBean();
    }
}
public class IOCTest {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    @Test
    public void testImport(){
        printBeans(applicationContext);
        Blue bean = applicationContext.getBean(Blue.class);
        System.out.println(bean);

        // 工厂Bean获取的是调用getObject创建的对象
        Object bean2 = applicationContext.getBean("colorFactoryBean");
        System.out.println("bean的类型:"+bean2.getClass()); // pos_1   输出:bean的类型:class com.atguigu.bean.Color

        Object bean4 = applicationContext.getBean("&colorFactoryBean");
        System.out.println(bean4.getClass()); // pos_2  输出:class com.atguigu.bean.ColorFactoryBean
    }

    private void printBeans(AnnotationConfigApplicationContext applicationContext){
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }
}
输出:
colorFactoryBean
ColorFactoryBean...getObject...
bean的类型:class com.atguigu.bean.Color
class com.atguigu.bean.ColorFactoryBean

推荐阅读文章:

Spring系列之FactoryBean(一)

BeanFactory和FactoryBean的区别

一文搞懂BeanFactory和FactoryBean区别

生命周期

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

测试类

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

MainConfigOfLifeCycle

@ComponentScan("com.atguigu.bean")
@Configuration
public class MainConfigOfLifeCycle {
    // 指定初始化和销毁方法
	@Bean(initMethod="init",destroyMethod="detory")
     public Car car(){
		return new Car();
	}
}

InitializingBean和DisposableBean

InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet()方法,凡是实现该接口的子类,在初始化bean的时候会执行该方法。

DisposableBean中的destroy()会在调用的程序结束以后,调用该方法内的程序

@PostConstruct和@PreDestroy

主要实现Bean在初始化之后、销毁之前执行自定义业务。 Spring负责管理Bean的初始化和销毁,但同时也提供方式让我们在bean初始化之后、销毁之前执行特定业务。之前一般需要实现相应接口实现,InitializingBean接口并实现 afterPropertiesSet() 方法, DisposableBean接口的destroy()方法。前者用于初始化bean业务,后者通常用于清理资源逻辑。
其实除了实现接口,Spring也提供了@PostConstruct 和 @PreDestroy 注解,使得实现更简单。

@PostConstruct

Spring会在初始化bean属性之后,调用一次拥有@PostConstruct注解的方法。该方法可以为任何访问级别,但不能为static。
下面示例使用@PostConstruct注解填充数据库,即在开发阶段,增加一些确实用户:

@Component
public class DbInit {
    @Autowired
    private UserRepository userRepository;
    
    @PostConstruct
    private void postConstruct() {
        User admin = new User("admin", "admin password");
        User normalUser = new User("user", "user password");
        userRepository.save(admin, normalUser);
    }
}
// 该示例首先初始化userRepository,然后调用postConstruct()方法。

@PreDestroy

使用@PreDestroy注解的方法仅执行异常,即在Spring从应用上下文中删除bean之前。与@PostConstruct注解一样,该方法可以为任何访问级别,但不能为static。

@Component
public class UserRepository {
    private DbConnection dbConnection;
    
    @PreDestroy
    public void preDestroy() {
        dbConnection.close();
    }
}
// 该方法一般用于在bean销毁之前释放资源或执行其他清理任务,如关闭数据库连接。
自JDK 9开始,@PostConstruct和@PreDestroy已被标注为@Deprecated,在JDK 11中已经被移除
#### **BeanPostProcessor**

BeanPostProcessor是Spring IOC容器给我们提供的一个扩展接口。接口声明如下:

public interface BeanPostProcessor {
    // bean初始化方法调用前被调用
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    // bean初始化方法调用后被调用
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

运行顺序:

Spring IOC容器实例化Bean → 调用BeanPostProcessor的postProcessBeforeInitialization方法 → 调用bean实例的初始化方法 → 调用BeanPostProcessor的postProcessAfterInitialization方法

BeanPostProcessor实例

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

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean);
        return bean;
    }
}

BeanFactoryPostProcessor简介

bean工厂的bean属性处理容器,说通俗一些就是可以管理我们的bean工厂内所有的beandefinition(未实例化)数据,可以随心所欲的修改属性。

属性赋值

@Value

该注解的作用是将配置文件的属性读出来,有**@Value(“${}”)@Value(“#{}”)**两种方式。

${}:注入的是外部配置文件对应的property

#{}:SpEL表达式对应的内容

@Data
@AllArgsConstructor
@NoArgsConstructo
public class Person {   
   // 使用@Value赋值;
   // 1、基本数值
   // 2、可以写#{}
   // 3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值)  
   @Value("张三")
   private String name;
   @Value("#{20-2}")
   private Integer age;
   @Value("${person.nickName}")
   private String nickName; 
}

@PropertySource

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

自动装配

@Autowired

默认按类型装配,找不到或者找到多个则报错。如果要按名称装配,需要结合Spring另外一个注解Qualifier(“name”)使用。默认必须装配requred=true,如果可以为空,可以设置为false,在Spring4.x结合jdk8.x的情况下还可以使用Optional和false同等的效果

@Controller
public class BookController {   
   @Autowired	// 自动注入属性
   private BookService bookService;	
}

@Resource

有两个关键的属性:name-名称,type-类型

  1. 如果指定了name,type,则从Spring容器中找一个名称和类型相当应的一个bean,找不到则报错。

  2. 如果只指定了name,则从Spring容器中找一个名称和name一样的bean,找不到则报错。

  3. 如果只指定了type,则从Spring容器中找一个类型和type一样的bean,找不到或者找到多个则报错。

  4. 如果没有指定参数,则默认找字段名称装配,找不到则按类型装配,找不到则报错。

@Inject

与@Autowired类似,可以完全代替@Autowired,但这个没有required属性,要求bean必须存在。如果要按名称装配,需要结合javax另外一个注解Named使用

@Qualifier

限定哪个bean应该被自动注入

假设有两个bean定义为Person类的实例

<beanid="user1"class="com.test.User">
<property name="name"value="zhangsan"/></bean>
 
<beanid="user2"class="com.test.User">
<property name="name"value="lisi"/></bean>

然后注入属性:

public class Staff{    
    @Autowired    
    private user user;
}

Spring 知道哪个bean应该自动注入吗?不。当运行上面的例子时,抛出如下异常:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:No unique bean of type [com.test.User] is defined:expected single matching bean but found2: [user1, user2]

要解决以上问题,需要使用@Quanlifier注解告诉Spring 哪个bean应该被Autowired。

public class Staff {
    @Autowired
    @Qualifier("user1")
    private User user;
}

@Primary

在Spring中使用注解@Autowired, 默认是根据类型Type来自动注入的。但有些特殊情况,同一个接口可能会有几种不同的实现类,而默认只会采取其中一种,@Primary 的作用就出来了。

// 有一个Singer接口
public interface Singer {
    String sing(String lyrics);
}
// Singer接口有两个实现类
@Component 
public class MetalSinger implements Singer{
    @Override
    public String sing(String lyrics) {
        return "I am singing with DIO voice: " + lyrics;
    }
}

@Component
public class OperaSinger implements Singer {
    @Override
    public String sing(String lyrics) {
        return "I am singing in Bocelli voice: "+lyrics;
    }
}
// 注入
@Component
public class SingerServiceImpl {
	private static final Logger logger = LoggerFactory.getLogger(SingerServiceImpl.class);
	@Autowired
	private Singer singer;
     
	public String sing(){
		return singer.sing("song lyrics");
	}
}

报错:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [main.service.Singer] is defined: expected single matching bean but found 2: metalSinger,operaSinger

原因:

Spring 根据类型无法选择到底注入哪一个。这个时候**@Primay** 可以闪亮登场了。

@Primary
@Component
public class OperaSinger implements Singer{
    @Override
    public String sing(String lyrics) {
        return "I am singing in Bocelli voice: " + lyrics;
    }
}

当然也可以使用上面提到的@Qualifier注解

@Resource

@Component 
@Qualifier("metalSinger")
public class MetalSinger implements Singer{
    @Override
    public String sing(String lyrics) {
        return "I am singing with DIO voice: "+lyrics;
    }
}
 
@Component
@Qualifier("opreaSinger")
public class OperaSinger implements Singer {
    @Override
    public String sing(String lyrics) {
        return "I am singing in Bocelli voice: "+lyrics;
    }
}

文章作者: Prannt
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Prannt !
评论
  目录