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 {@Beanpublic User user(){return new User().setName("李四").setAge(18);}@Beanpublic 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// 测试二:测试组件中注入的是否是容器中的BeanSchool 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(...)}) // 支持指定多个扫描路径的@ComponentScanpublic 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*/@Overridepublic 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@Configurationpublic class MyConfig{@Bean // id 为 userpublic User user(){return new User();}@Lazy // 懒加载@Bean(name = "pro") // id为propublic Product product(){return new Product();}@Bean@Scope("prototype") // 创建多实例的Beanpublic 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{@Beanpublic 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);@Testpublic void test1() {String[] names = context.getBeanDefinitionNames();for (String name : names) {// 输出:// com.example.config.SubConfig,即使用 @Import 进行加载会使用类全限定名作为id// user:说明子配置类中配置生效了System.out.println(name);}}@Testpublic void test2() {// 通过id获取beanUser 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// 支持导入配置类,且配置类不需要标注@Configurationreturn 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() 注册进去*/@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 如果容器中不存在user这个Bean,则注册一个if (!registry.containsBeanDefinition("user")) {// Bean的定义信息RootBeanDefinition beanDefinition = new RootBeanDefinition(User.class);// user:需要注册的Bean的id// beanDefinition:BeanDefinition的实现类,用来定义一个Beanregistry.registerBeanDefinition("user", beanDefinition);}}}// 在配置类上进行导入@Configuration@Import({MyImportBeanDefinitionRegistrar.class})public class MyConfig{}
使用 Spring 提供的 FactoryBean
FactoryBean 接口的方法:
public interface FactoryBean<T> {String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";// 返回创建的Bean@NullableT getObject() throws Exception;// 返回创建的Bean的类型@NullableClass<?> 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 {
@Beanpublic MyFactoryBean myFactoryBean(){return new MyFactoryBean();}
}
// 测试,通过id获取相应的Bean public class DemoTest { private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
@Testpublic void test() {// 通过工厂Bean的id获取的是调用getObject()方法创建的对象:class com.example.entity.UserObject myFactoryBean = context.getBean("myFactoryBean");System.out.println(myFactoryBean.getClass());// 如果需要获取工厂bean本身,可以使用&id获取:class com.example.common.MyFactoryBeanObject bean = context.getBean("&myFactoryBean");System.out.println(bean.getClass());}
} ```
