spring 配置类
Spring 使用 @Configuration 注解来替代之前的 springContext.xml 文件:
- @Configuration:注解在类上,声明一个配置类,等同于之前的一个 spring-context.xml 配置文件
- @ComponentScans、@ComponentScan:自动装配,用于声明要扫描的包路径 ```java @Configuration // 声明一个配置类,等同于spring-context.xml配置文件 public class MyConfig{ // ….. }
public class Main { public static void main(String[] args) { // 通过配置类创建Spring容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class); context.close(); } }
@Configuration 注解 proxyBeanMethods 属性的作用:
- 类组件之间没有依赖关系使用 Lite 模式加速容器启动过程,减少判断,即 proxyBeanMethods=false
- 类组件之间有依赖关系使用 Full 模式,方法会被调用得到之前的单实例组件,即 proxyBeanMethods=true,默认
```java
// 创建两个pojo,其中Schoo的属性中含有User类
@Data
@Accessors(chain = true)
public class User {
private String name;
private Integer age;
}
@Data
@Accessors(chain = true)
public class School {
private String name;
private User user;
}
// 创建Spring配置类
@Configuration(proxyBeanMethods = true) //声明配置类
public class DemoConfig {
@Bean
public User user(){
return new User().setName("李四").setAge(18);
}
@Bean
public School school(){
// 组件School依赖了User组件
// 使用方法名
return new School().setUser(user()).setName("校长");
}
}
// 测试:
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(DemoConfig.class);
// 测试一:测试从容器中获取的是否是同一对象
User user = context.getBean("user", User.class);
User user1 = context.getBean("user", User.class);
System.out.println(user == user1); //始终返回true
// 测试二:测试组件中注入的是否是容器中的Bean
School school = context.getBean("school", School.class);
//当proxyBeanMethods设置为true时,返回true,当proxyBeanMethods设置为false时,返回false
// 说明默认情况下spring会对@Bean标注的方法进行代理,使得组件存在依赖时,依赖获取的组件为容器中的单例对象
// 当proxyBeanMethods设置为false时,spring不会对@Bean标注的方法进行代理,导致组件存在依赖时,创建的是不同对象,且该对象不被容器管理
System.out.println(school.getUser() == user);
context.close();
}
组件注册
Spring 支持多种方式注册组件:
- 包扫描 + 组件标注注解(@Controller、@Service、@Repository、@Component):一般开发过程均使用此种方式
- 使用 @Bean 声明:一般用于引入第三方 jar 包,进行第三方 jar 的整合
- 使用 @Import 导入:快速给容器中导入一个组件
- 使用 Spring 提供的 FactoryBean (工厂Bean)
组件属性
组件 Bean 的单例与多例:默认是单例,通过 @Scope 注解来进行设置
- @Scope 注解的取值:
- ConfigurableBeanFactory#SCOPE_PROTOTYPE:prototype 多实例
- ConfigurableBeanFactory#SCOPE_SINGLETON:singleton 单实例
- org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST:request 同一次请求创建一个实例
- org.springframework.web.context.WebApplicationContext#SCOPE_SESSION:session 同一个 session 创建一个实例
- Spring 中单例默认是随着 Spring 工厂创建时;如果需要单例在使用时才创建,需要使用 @Lazy 注解
- Spring 中多例在 Spring 工厂时不创建,而是在使用时才创建
组件的选择性导入
组件的选择性导入:@Conditional 注解
- @Conditional( { Condition.class } ):按照一定的条件进行判断,满足条件给容器中注册 Bean,条件类需要实现 Condition 接口
@Conditional 注解可以注解在配置类或者方法上
- 注解在配置类上时,表示只有满足当前条件,该配置类中的所有配置才会生效
注解在方法上时,表示只有满足当前条件,该方法声明的 Bean 才会被导入 ```java // 自定义一个条件类:表示只有当前操作系统是Windows时才导入该配置类 public class WindowsCondition implements Condition { /*
- @param context 判断条件能使用的上下文环境
- @param metadata 注释信息
@return */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 判断是否为Windows系统 // 1.能够获取到ioc使用的beanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); // 2.获取到类加载器 ClassLoader classLoader = context.getClassLoader(); // 3.获取到资源加载器 ResourceLoader resourceLoader = context.getResourceLoader(); // 4.获取当前环境信息 Environment environment = context.getEnvironment(); // 5.获取到bean定义的注册类 BeanDefinitionRegistry registry = context.getRegistry();
// 可以判断容器中bean的注册情况,也可以给容器中注册bean // 可以在JVM运行参数中使用-Dos.name=linux来控制该参数 String property = environment.getProperty(“os.name”); if (property != null && property.contains(“Windows”)){
return true;
} return false; } }
// 使用该条件 @Configuration @Conditional({WindowsCondition.class}) public class MyConfig{ // …. }
<a name="E0JWo"></a>
#### 包扫描+组件标注注解
组件标注注解:
- @Component:通用
- @Repository、@Service、@Controller:用在业务层
组件扫描注解:注解在配置类(@Configuration注解的类)上,用于指定扫描路径和扫描条件,相当于配置文件中的 <context:component-scan>
- @ComponentScan:可重复注解
- @ComponentScans:支持在里面写入多个 @ComponentScan 注解,@ComponentScan 重复的载体
```java
@Configuration // 声明配置类
@ComponentScans(value = {@ComponentScan(...), @ComponentScan(...)}) // 支持指定多个扫描路径的@ComponentScan
public class MyConfig {
// ....
}
@ComponentScan 注解的常用参数:
- basePackages / value:字符串数组,指定包扫描路径,可以是单个路径或者多个路径
- basePackageClasses:指定具体扫描的类,支持单个或多个 class
- nameGenerator:指定 bean 名称即 id 的生成器,默认是 BeanNameGenerator
- scopeResolver:处理检测到的 Bean 的 scope 范围
- scopedProxy:是否为指定到的组件生成代理
- useDefaultFilters:是否对 @Component、@Repository、@Service、@Controller 注解的类开启检测,默认为 true
- includeFilters:指定扫描时需要扫描哪些需要的组件,支持如下的 FilterType
- FilterType.ANNOTATION:按照注解进行筛选,默认
- FilterType.ASSIGNABLE_TYPE:按照给定的类型
- FilterType.ASPECTJ:使用 AspectJ 表达式
- FilterType.REGEX:使用正则表达式
- FilterType.CUSTOM:自定义类型,必须为 TypeFilter 的实现类
- excludeFilters:排除某些扫描到的类
- lazyInit:扫描到的类都开启懒加载,默认不开启 ```java // 扫描包 @ComponentScan({“com.example.service” ,”com.example.entity”})
// 指定具体的类 @ComponentScan(basePackageClasses = {User.class ,UserService.class})
// 扫描自定义的注解@MyAnnotation @ComponentScan( basePackages=”com.example”, includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {MyAnnotation.class}) } )
// 自定义扫描规则:需要实现 TypeFilter 接口 public class MyFilter implements TypeFilter{ /*
* @param metadataReader 读取到的当前正在扫描类的信息
* @param metadataReaderFactory 可以获取其他任何类信息
* @return
* @throws IOException
*/
@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();
return false;
}
}
<a name="5vHG2"></a>
#### 使用 @Bean 声明
在配置类中的方法上添加 @Bean 注解,可以声明一个 Bean,一般用于第三方 jar 包的整合:
- @Bean 注解声明的 Bean 的 id:
- 当 @Bean 注解不加参数时,默认以**方法名**作为 id
- 当 @Bean(name ="xx") 中有定义时,则采用定义中的名字
- @Bean 注解声明的 Bean 默认是单实例,如果需要创建多实例的 Bean ,则需要在方法上添加 @Scope 注解,@Scope 注解支持如下取值:
- ConfigurableBeanFactory#SCOPE_PROTOTYPE:prototype 多实例
- ConfigurableBeanFactory#SCOPE_SINGLETON:singleton 单实例
- org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST:request 同一次请求创建一个实例
- org.springframework.web.context.WebApplicationContext#SCOPE_SESSION:session 同一个 session 创建一个实例
- 单实例的 Bean 默认在Spring工厂创建时进行创建,多实例的 Bean 会在使用时才会进行创建
```java
@Configuration
public class MyConfig{
@Bean // id 为 user
public User user(){
return new User();
}
@Lazy // 懒加载
@Bean(name = "pro") // id为pro
public Product product(){
return new Product();
}
@Bean
@Scope("prototype") // 创建多实例的Bean
public Student student(){
return new Student();
}
}
使用 @Import 进行注册
@Import 支持如下情况的组件导入:
- @Import( User.class ):直接导入某个类,其导入的 Bean 的 id 为该类的全限定名,如 com.example.entity.User
- @Import( ImportSelector.class ):ImportSelector的实现类有一个 selectImports() 方法,返回的是需要导入到容器中的全类名数组
- @Import( ImportBeanDefinitionRegistrar.class ):ImportBeanDefinitionRegistrar 的实现类中有一个 registerBeanDefinitions 方法,支持在里面进行 Bean 的注册
1、@Import({ User.class }):直接导入类,一般用于导入另外的配置类
// 实体类
@Data(staticConstructor = "of") // 静态创建
@Accessors(chain = true) // 支持链式调用
public class User {
private String name;
private Integer age;
}
// 要被导入的配置类,该类可以不用 @Configuration 声明
public class SubConfig{
@Bean
public User user(){
return User.of().setName("Bob").setAge(19);
}
}
// 配置类
@Configuration
@Import({ SubConfig.class })
public class Config1 {
}
// 测试
public class Config1Test {
// 通过配置类创建Spring工厂
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config1.class);
@Test
public void test1() {
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
// 输出:
// com.example.config.SubConfig,即使用 @Import 进行加载会使用类全限定名作为id
// user:说明子配置类中配置生效了
System.out.println(name);
}
}
@Test
public void test2() {
// 通过id获取bean
User bean = context.getBean("user", User.class);
// 输出:User(name=Bob, age=19)
System.out.println(bean);
User user = context.getBean(User.class);
System.out.println(user);
}
}
2、@Import( ImportSelector.class ):ImportSelector的实现类有一个 selectImports() 方法,返回的是需要导入到容器中的全类名数组
- 通过这种方式导入的类,其 id 为其全限定类名 ```java /**
- @param importingClassMetadata 当前标注@Import注解类的所有注解信息
- @return 返回值为需要导入到容器中的全类名数组
*/
public class MySelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
} }// 可以返回空数组,不能返回null
// 支持导入配置类,且配置类不需要标注@Configuration
return new String[]{"com.example.config.Config2"};
// 在配置类上使用 @Configuration @Import({MySelector.class}) public class Config3 { }
3、@Import( ImportBeanDefinitionRegistrar.class ):ImportBeanDefinitionRegistrar 的实现类中有一个 registerBeanDefinitions 方法,支持在里面进行 Bean 的注册,手动注册
```java
// ImportBeanDefinitionRegistrar实现类
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的注册信息
* @param registry BeanDefinition注册类,把所有需要添加到容器中的 Bean 通过 registry.registerBeanDefinition() 注册进去
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 如果容器中不存在user这个Bean,则注册一个
if (!registry.containsBeanDefinition("user")) {
// Bean的定义信息
RootBeanDefinition beanDefinition = new RootBeanDefinition(User.class);
// user:需要注册的Bean的id
// beanDefinition:BeanDefinition的实现类,用来定义一个Bean
registry.registerBeanDefinition("user", beanDefinition);
}
}
}
// 在配置类上进行导入
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
public class MyConfig{}
使用 Spring 提供的 FactoryBean
FactoryBean 接口的方法:
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
// 返回创建的Bean
@Nullable
T getObject() throws Exception;
// 返回创建的Bean的类型
@Nullable
Class<?> getObjectType();
// 是否是单例,true 单例,false 多例
default boolean isSingleton() {
return true;
}
}
使用:
- 通过工厂Bean的id获取到的是工厂Bean中getObject返回的对象
通过工厂Bean的&id获取到的是工厂类本身 ```java // 创建一个FactoryBean的实现类 public class MyFactoryBean implements FactoryBean
{ @Override public User getObject() throws Exception { return User.of().setAge(18).setName("Hello");
}
@Override public Class<?> getObjectType() {
return User.class;
} }
// 配置类中进行配置 @Configuration public class MyConfig {
@Bean
public MyFactoryBean myFactoryBean(){
return new MyFactoryBean();
}
}
// 测试,通过id获取相应的Bean public class DemoTest { private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
@Test
public void test() {
// 通过工厂Bean的id获取的是调用getObject()方法创建的对象:class com.example.entity.User
Object myFactoryBean = context.getBean("myFactoryBean");
System.out.println(myFactoryBean.getClass());
// 如果需要获取工厂bean本身,可以使用&id获取:class com.example.common.MyFactoryBean
Object bean = context.getBean("&myFactoryBean");
System.out.println(bean.getClass());
}
} ```