在springboot的开发中,是比较推荐使用注解开发的,而它的底层,有很多地方都用到了spring的东西,所以我觉得有必要学习一下spring的注解开发及其相关原理。以下均属于个人学习笔记,在此记录,以便日后之用,希望对正在阅读本篇博客的你有所帮助。现在主要讲讲组件注册:
一、创建maven工程,导入相关依赖,如下是我的pom.xml文件:
<?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.qxf</groupId>
<artifactId>spring-annotation</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
二、通过@Bean方式注册
1.创建实体类Person,如下:
package com.qxf.pojo;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.pojo
*/
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 +
'}';
}
}
2.创建配置类ComponentRegistryConfig,如下
package com.qxf.config;
import com.qxf.pojo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.config
*/
@Configuration //表示这是一个配置类
public class ComponentRegistryConfig {
@Bean
public Person person123(){
return new Person("张三",22);
}
}
3.创建测试类ComponentRegistryConfigTest,如下:
package com.qxf.test;
import com.qxf.config.ComponentRegistryConfig;
import com.qxf.pojo.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.test
*/
public class ComponentRegistryConfigTest {
//读取配置文件,获取配置信息
private ApplicationContext context = new AnnotationConfigApplicationContext(ComponentRegistryConfig.class);
@Test
public void testBeanAnnotation(){
Person person = (Person) context.getBean("person123");
System.out.println("testBeanAnnotation---"+person);
}
}
4.运行测试方法testBeanAnnotation(),可在控制台看到如下结果:
说明测试通过,组件注册成功的。
关于获取bean的方式,主要有2种:
(1)根据名称获取:这个名称是唯一的,例如上面测试类的,就是根据名称获取
Person person = (Person) context.getBean("person123");
(2)根据类型获取,使用这种方法,有一个注意点,一个类的bean实例可能不止一个,如果有多个就会报错,建议使用根据名称获取,这个当做了解
Person bean = context.getBean(Person.class);
好处就是,如果容器中该类只有一个bean的时候,可以不同进行类型强制转换,写起来方便一点点。
5.几个注解的作用
(1)@Configuration,表示这是一个配置类,在spring配置中,相当于以前的applicationContext.xml文件,其实这个注解,也可以用在其他配置信息的配置类上,比如mybatis配置,shiro配置,都可以用这个注解来代替以前的xml配置文件,主要还是用于读取配置信息。
(2)@Bean,这个注解就表示向spring容器注册组件,其作用相当于在applicationContext.xml文件中的<bean>标签,bean的名称默认是方法名,也可以自定义,如@Bean("person11"),那么bean的名称不再是方法名称,而是person11。
6.@Scope注解,修改bean作用域
默认情况下,spring容器的bean是单例的:
可以看到,打印结果为true,2个bean,其实是同一个bean。如果你不想使用单例的效果,有其他的需求,则可以通过@Scope注解,来修改bean的作用域,其有4种取值,这里就不演示了,对象的创建时机略有不同:
(1)singleton,单例,默认值,在创建IOC容器时就创建了对象,可以通过@Lazy实现懒加载,这样就会推出对象的创建时机,在第一次使用对象的时候才创建并初始化
(2)prototype,多例,在创建IOC容器时,并不会创建对象,而是在每次获取bean对象的时候,才创建对象
(3)request,web环境中使用,每次http请求创建对象
(4)session,web环境中使用,每个会话中只有一个对象
三、包扫描+组件注解(ComponentScan+Controller||Service||Repository||Component)注册
1.在(二)的基础上,新建以下几个类:
(1)PersonDao
package com.qxf.dao;
import org.springframework.stereotype.Repository;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.dao
*/
@Repository
public class PersonDao {
}
(2)PersonService
package com.qxf.service;
import org.springframework.stereotype.Service;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.service
*/
@Service
public class PersonService {
}
(3)PersonController
package com.qxf.controller;
import org.springframework.stereotype.Controller;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.controller
*/
@Controller
public class PersonController {
}
(4)修改后的配置类ComponentRegistryConfig(添加了一个包扫描)
package com.qxf.config;
import com.qxf.pojo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.config
*/
@Configuration //表示这是一个配置类
@ComponentScan(basePackages = "com.qxf") //包扫描
public class ComponentRegistryConfig {
@Scope("prototype") //默认为singleton
@Bean
public Person person123(){
return new Person("张三",22);
}
}
(5)测试方法
@Test
public void testComponentScan(){
String[] beanDefinitionNames = context.getBeanDefinitionNames();
System.out.println("下面是已定义的bean:");
for (String name : beanDefinitionNames){
System.out.println(name);
}
}
(6)测试结果
可以看到,spring容器中,除了自定义的bean之外,还有一些内部的bean。
在web开发中用得比较多的@Repository,@Service,@Controller注解,其bean的默认名称为类名首字母小写。
如果你不想扫描全部的注解,可以采用以下方法:
(1)使用excludeFilters,指定要排除的
@ComponentScan(value = "com.qxf",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
Controller.class, Service.class
})
})
其中,type = FilterType.ANNOTATION,表示根据注解类型进行排除
而,classes = {Controller.class, Service.class },则表示要排除的注解,这里表示要排除掉@Service,@Controller注解的bean
(2)使用includeFilters,指定要留下的
@ComponentScan(value = "com.qxf",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
Controller.class
}),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {
PersonService.class
})
},useDefaultFilters = false)
与排除规则不同的是,要将默认过滤规则设置为false:useDefaultFilters = false,否则不起效果。
(3)自定义过滤规则
@ComponentScan(value = "com.qxf",includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
},useDefaultFilters = false)
其中,MyTypeFilter要实现TypeFilter接口,具体例子如下:
package com.qxf.config;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/7
* @Description: com.qxf.config
*/
public class MyTypeFilter implements TypeFilter{
//返回值为true,表示匹配成功,false,表示不匹配
@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",则返回true
if(className.contains("service")){
return true;
}
return false;
}
}
四、@Import快速导入外部类到容器
@Import注解在springboot中运用比较多,所以也一起介绍一下。
1.新建如下类(包括修改的类)
(1)Student
package com.qxf.pojo;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.pojo
*/
public class Student {
}
(2)Grade
package com.qxf.pojo;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.pojo
*/
public class Grade {
}
(3)Teacher
package com.qxf.pojo;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.pojo
*/
public class Teacher {
}
(4)MyImportSelector,自定义bean选择器,实现ImportSelector接口,返回要创建的bean的全类名的数组
package com.qxf.condition;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: 自定义导入bean选择器
*/
public class MyImportSelector implements ImportSelector{
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{
"com.qxf.pojo.Student"
};
}
}
(5)MyImportBeanDefinitionRegistrar,根据条件手动注册bean,要实现ImportBeanDefinitionRegistrar接口
package com.qxf.condition;
import com.qxf.pojo.Grade;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: 手动注册bean
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
//通过BeanDefinitionRegistry手动控制bean注册到IOC容器中
boolean b = registry.containsBeanDefinition("com.qxf.pojo.Student");
//如果IOC容器中有com.qxf.pojo.Student的bean
if(b){
//就将成绩注册为bean
RootBeanDefinition beanDefinition = new RootBeanDefinition(Grade.class);
registry.registerBeanDefinition("grade",beanDefinition);
}
}
}
(6)修改配置类,添加@Import注解,导入bean
package com.qxf.config;
import com.qxf.condition.MyImportBeanDefinitionRegistrar;
import com.qxf.condition.MyImportSelector;
import com.qxf.pojo.Person;
import com.qxf.pojo.Student;
import com.qxf.pojo.Teacher;
import org.springframework.context.annotation.*;
/**
* @Auther: qiuxinfa
* @Date: 2019/12/15
* @Description: com.qxf.config
*/
@Configuration //表示这是一个配置类
@ComponentScan(basePackages = "com.qxf") //包扫描
@Import({Teacher.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class ComponentRegistryConfig {
@Scope("prototype") //默认为singleton
@Bean
public Person person123(){
return new Person("张三",22);
}
}
(7)测试结果
可以看到,容器中多了3个bean