Spring中常用的注解
4.3.1 @ComponentScan和ComponentScans(bean批量注册)
- @ComponentScan用于批量注册bean,spring会按照这个注解的配置,递归扫描指定包中的所有类,将满足条件的类批量注册到spring容器中
- 可以通过value、basePackages、basePackageClasses 这几个参数来配置包的扫描范围
- 可以通过useDefaultFilters、includeFilters、excludeFilters这几个参数来配置类的过滤器,被过滤器处理之后剩下的类会被注册到容器中
- 指定包名的方式配置扫描范围存在隐患,包名被重命名之后,会导致扫描实现,所以一般我们在需要扫描的包中可以创建一个标记的接口或者类,作为basePackageClasses的值,通过这个来控制包的扫描范围
- @CompontScan注解会被ConfigurationClassPostProcessor类递归处理,最终得到所有需要注册的类
4.3.1.1 源码定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class) //@1
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
}
常用参数:
- value:指定需要扫描的包,如:com.javacode2018
- basePackages:作用同value;value和basePackages不能同时存在设置,可二选一
- basePackageClasses:指定一些类,spring容器会扫描这些类所在的包及其子包中的类
- nameGenerator:自定义bean名称生成器
- resourcePattern:需要扫描包中的那些资源,默认是:**/*.class,即会扫描指定包中所有的class文件
- useDefaultFilters:对扫描的类是否启用默认过滤器,默认为true
- includeFilters:过滤器:用来配置被扫描出来的那些类会被作为组件注册到容器中
- excludeFilters:过滤器,和includeFilters作用刚好相反,用来对扫描的类进行排除的,被排除的类不会被注册到容器中
- lazyInit:是否延迟初始化被注册的bean
- @Repeatable(ComponentScans.class),这个注解可以同时使用多个
4.3.1.2 工作过程:
- Spring会扫描指定的包,且会递归下面子包,得到一批类的数组
- 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中
4.3.1.3 关键问题:
- 需要扫描哪些包?
通过value、backPackages、basePackageClasses这3个参数来控制 - 过滤器有哪些?
通过useDefaultFilters、includeFilters、excludeFilters这3个参数来控制过滤器
4.3.1.4 扫描规则:
默认情况下,任何参数都不设置的情况下会将@ComponentScan修饰的类所在的包作为扫描包。
默认情况下,useDefaultFilters=true,spring容器内部会使用默认过滤器,规则是:凡是类上有@Repository、@Service、@Controller、@Component这几个注解中的任何一个的,那么这个类就会被作为bean注册到spring容器中,所以默认情况下,只需在类上加上这几个注解中的任何一个,这些类就会自动交给spring容器来管理了。
4.3.1.5 案例1:任何参数未设置
分别在dao,controller,service包下创建类,用@Service,@Controller,@Repository注解标注
UserService
package com.zjhc.componentSacn.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {}
UserController
package com.zjhc.componentSacn.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {}
UserDao
package com.zjhc.componentSacn.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {}
UserModel
package com.zjhc.componentSacn;
import org.springframework.stereotype.Component;
@Component
public class UserModel {}
ScanBean
package com.zjhc.componentSacn;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class ScanBean {}
测试
@Test
public void test2(){
ApplicationContext context = new AnnotationConfigApplicationContext(ScanBean.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName+"---->"+context.getBean(beanName));
}
}
使用AnnotationConfigApplicationContext作为ioc容器,将ScanBean.class作为参数传入,默认会扫描ScanBean类所在的包中的所有类,类上有@Component、@Repository、@Service、@Controller任何一个注解的都会被注册到容器中
4.3.1.6 案例2:指定需要扫描的包
指定需要扫毛哪些包,可以通过value或者basePackage来配置,二者选其一,都配置运行会报错,下面我们通过value来配置
package com.zjhc.componentSacn;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan({"com.zjhc.componentSacn.controller,
com.zjhc.componentSacn.dao"})
public class ScanBean {}
测试结果
4.3.1.7 案例:basePackageClasses指定扫描范围
指定包名的方式扫描存在的一个隐患,若包被重名了,会导致扫描会失效,我们可以在需要扫描的包中定义一个标记的接口或者类,他们的唯一的作用是作为basePackageClasses的值,其他没有任何用途。
定义一个类或者接口
package com.zjhc.componentSacn.controller;
public class a {}
import com.zjhc.componentSacn.controller.a;
import org.springframework.context.annotation.ComponentScan;
//@ComponentScan({"com.zjhc.componentSacn.controller,com.zjhc.componentSacn.dao"})
@ComponentScan(basePackageClasses = a.class)
public class ScanBean {
}
scanBean---->com.zjhc.componentSacn.ScanBean@67e2d983
userController---->com.zjhc.componentSacn.controller.UserController@5d47c63f
4.3.1.8 includeFilters和excludeFilters的使用
是一个Filter类型的数组,多个Filter之间为或者关系,即满足任意一个就可以了,看一下Filter的代码:
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
主要参数:
type:过滤器的类型,是个枚举类型,5种类型
ANNOTATION:通过注解的方式来筛选候选者,即判断候选者是否有指定的注解
ASSIGNABLE_TYPE:通过指定的类型来筛选候选者,即判断候选者是否是指定的类型
ASPECTJ:ASPECTJ表达式方式,即判断候选者是否匹配ASPECTJ表达式
REGEX:正则表达式方式,即判断候选者的完整名称是否和正则表达式匹配
CUSTOM:用户自定义过滤器来筛选候选者,对候选者的筛选交给用户自己来判断
value:和参数classes效果一样,二选一
classes:3种情况如下
当type=FilterType.ANNOTATION时,通过classes参数可以指定一些注解,用来判断被扫描的类上是否有classes参数指定的注解
当type=FilterType.ASSIGNABLE_TYPE时,通过classes参数可以指定一些类型,用来判断被扫描的类是否是classes参数指定的类型
当type=FilterType.CUSTOM时,表示这个过滤器是用户自定义的,classes参数就是用来指定用户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接口
pattern:2种情况如下
当type=FilterType.ASPECTJ时,通过pattern来指定需要匹配的ASPECTJ表达式的值
当type=FilterType.REGEX时,通过pattern来自正则表达式的值
4.3.1.8.1 扫描包含注解的类
我们自定义一个注解,让标注有这些注解的类自动注册到容器中
package com.zjhc.componentSacn.annotation;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
}
创建一个类,使用这个注解标注
package com.zjhc.componentSacn.annotation;
@MyAnno
public class Service1 {}
再来一个类,使用spring中的@Compontent
标注
package com.zjhc.componentSacn.annotation;
import org.springframework.stereotype.Component;
@Component
public class Service2 {}
再来一个类,使用@CompontentScan标注
package com.zjhc.componentSacn;
@ComponentScan(includeFilters = {@ComponentScan.Filter(type= FilterType.ANNOTATION,classes = MyAnno.class)})
public class ScanBean2 {}
测试用例
@Test
public void test3(){
ApplicationContext context = new AnnotationConfigApplicationContext(ScanBean2.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName+"---->"+context.getBean(beanName));
}
}
结果
问题:Service1上标注了@MyBean注解,被注册到容器了,但是没有标注@MyBean啊,怎么也被注册到容器了?
回答:@CompontentScan注解中的useDefaultFilters默认是true,表示会启用默认的过滤器,默认的过滤器会将标注有@Component、@Repository、@Service、@Controller这几个注解的类也注册到容器中。
修改扫描代码:
如果我们只想将标注有@MyBean注解的bean注册到容器,需要将默认过滤器关闭,即:useDefaultFilters=false
package com.zjhc.componentSacn;
@ComponentScan(useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type= FilterType.ANNOTATION,classes = MyAnno.class)})
public class ScanBean2 {}
再输出:
4.3.1.8.2 包含指定类型的类
被扫描的类满足IService.class.isAssignableFrom(被扫描的类)条件的都会被注册到spring容器中
@ComponentScan(
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE,classes = IService.class)}
)
接口
package com.zjhc.componentSacn.annotation.componentBytype;
public interface IService {}
实现类
package com.zjhc.componentSacn.annotation.componentBytype;
public class ServiceA implements IService {}
package com.zjhc.componentSacn.annotation.componentBytype;
public class ServiceB implements IService {}
@CompontentScan标注的类
package com.zjhc.componentSacn.annotation.componentBytype;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE,
classes = IService.class)})
public class ScanBean3 {}
测试:
@Test
public void test4(){
ApplicationContext context = new AnnotationConfigApplicationContext(ScanBean3.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName+"---->"+context.getBean(beanName));
}
}
4.3.1.8.3 自定义Filter
步骤:
1.设置@Filter中type的类型为:FilterType.CUSTOM
2.自定义过滤器类,需要实现接口:org.springframework.core.type.filter.TypeFilter
3.设置@Filter中的classses为自定义的过滤器类型
TypeFilter这个接口的定义:
是一个函数式接口,包含一个match方法,方法返回boolean类型,有2个参数,都是接口类型的,下面介绍一下这2个接口
@FunctionalInterface
public interface TypeFilter {
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException;
}
MetadataReader接口:
类元数据读取器,可以读取一个类上的任意信息,如类上面的注解信息、类的磁盘路径信息、类的class对象的各种信息,spring进行了封装,提供了各种方便使用的方法。
MetadataReader接口的定义:
public interface MetadataReader {
/**
* 返回类文件的资源引用
*/
Resource getResource();
/**
* 返回一个ClassMetadata对象,可以通过这个读想获取类的一些元数据信息,
* 如类的class对象、是否是接口、是否有注解、是否是抽象类、父类名称、接口名称、
* 内部包含的之类列表等等,可以去看一下源码
*/
ClassMetadata getClassMetadata();
/**
* 获取类上所有的注解信息
*/
AnnotationMetadata getAnnotationMetadata();
}
MetadataReaderFactory接口:
类元数据读取器工厂,可以通过这个类获取任意一个类的MetadataReader对象
MetadataReaderFactory接口定义:
public interface MetadataReaderFactory {
/**
* 返回给定类名的MetadataReader对象
*/
MetadataReader getMetadataReader(String className) throws IOException;
/**
* 返回指定资源的MetadataReader对象
*/
MetadataReader getMetadataReader(Resource resource) throws IOException;
}
案例:
需求:我们来个自定义的Filter,判断被扫描的类如果是IService接口类型的,就让其注册到容器中。
接口
package com.zjhc.componentSacn.annotation.componentBytype;
public interface IService {}
实现类
package com.zjhc.componentSacn.annotation.componentBytype;
public class ServiceA implements IService {}
package com.zjhc.componentSacn.annotation.componentBytype;
public class ServiceB implements IService {}
自定义的TypeFilter类:
package com.zjhc.componentSacn.annotation.componentByfilter;
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;
public class MyTypeFilter implements TypeFilter {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
Class clzz = null;
try {
//获取当前被扫描的类
clzz = Class.forName(metadataReader.getClassMetadata().getClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//判断clzz是否为IService类型
boolean result = IService.class.isAssignableFrom(clzz);
return result;
}
}
@CompontentScan标注的类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(type= FilterType.CUSTOM,
classes = MyTypeFilter.class)})
public class ScanBean4 {
}
4.3.1.9 @ComponentScan重复使用
@ComponentScans({
@ComponentScan(basePackageClasses = a.class),
@ComponentScan(
useDefaultFilters = false, //不启用默认过滤器
includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class)
})})
public class ScanBean3 {
}
4.3.2 @Import使用:bean的批量注册
@Import可以用来批量导入需要注册的各种类,如普通的类、配置类,然后完成普通类和配置类中所有bean的注册。
@Import可以使用在任何类型上,通常情况下,类和注解上用的比较多。
value:一个Class数组,设置需要导入的类,可以是@Configuration标注的列,可以是ImportSelector接口或者ImportBeanDefinitionRegistrar接口类型的,或者需要导入的普通组件类。
源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar},
* or regular component classes to import.
*/
Class<?>[] value();
}
@Import的value常见的有5种用法:
- value为普通的类
@Import({Service1.class, Service2.class})
public class MainConfig1 {}
com.demo24.test1.Service1->com..demo24.test1.Service1@7e0b85f9
com.demo24.test1.Service2->com.demo24.test1.Service2@63355449
通过@Import导入的2个类,bean名称为完整的类名,可以使用@Compontent注解指定被导入类的bean名称
@Component("service1")
public class Service1 {}
service1->com.demo24.test1.Service1@45efd90f
- value为@Configuration标注的类
package com.javacode2018.lesson001.demo24.test2;
//模块1配置类
@Configuration
public class ConfigModule1 {
@Bean
public String module1() { return "我是模块1配置类!"; }
}
package com.javacode2018.lesson001.demo24.test2;
//模块2配置类
@Configuration
public class ConfigModule2 {
@Bean
public String module2() { return "我是模块2配置类!"; }
}
package com.javacode2018.lesson001.demo24.test2;
//通过Import来汇总多个@Configuration标注的配置类
@Import({ConfigModule1.class, ConfigModule2.class}) //@1
public class MainConfig2 {}
mainConfig2->com.javacode2018.lesson001.demo24.test2.MainConfig2@ba2f4ec
com.javacode2018.lesson001.demo24.test2.ConfigModule1->com.javacode2018.lesson001.demo24.test2.ConfigModule1$$EnhancerBySpringCGLIB$$700e65cd@1c1bbc4e
module1->我是模块1配置类!
com.javacode2018.lesson001.demo24.test2.ConfigModule2->com.javacode2018.lesson001.demo24.test2.ConfigModule2$$EnhancerBySpringCGLIB$$a87108ee@55fe41ea
module2->我是模块2配置类!
- value为@CompontentScan标注的类
项目中分多个模块,每个模块有各自独立的包,我们在每个模块所在的包中配置一个@CompontentScan类,然后通过@Import来导入需要启用的模块
第一个组件的包及里面的类:
package com.javacode2018.lesson001.demo24.test3.module1;
@Component
public class Module1Service1 {}
package com.javacode2018.lesson001.demo24.test3.module1;
@Component
public class Module1Service2 {}
package com.javacode2018.lesson001.demo24.test3.module1;
//模块1的主键扫描
@ComponentScan
public class CompontentScanModule1 {}
第二个组件的包及里面的类:
package com.javacode2018.lesson001.demo24.test3.module2;
@Component
public class Module2Service1 {}
package com.javacode2018.lesson001.demo24.test3.module2;
@Component
public class Module2Service2 {}
package com.javacode2018.lesson001.demo24.test3.module2;
// 模块2的组件扫描
@ComponentScan
public class CompontentScanModule2 {}
总配置类:通过@Import导入每个模块中的组件扫描类
/**
* 通过@Import导入多个@CompontentScan标注的配置类
*/
@Import({CompontentScanModule1.class, CompontentScanModule2.class}) //@1
public class MainConfig3 {}
- value为ImportBeanDefinitionRegistrar接口类型
1. 定义ImportBeanDefinitionRegistrar接口实现类,在registerBeanDefinitions方法中使用registry来注册bean
2. 使用@Import来导入步骤1中定义的类
3. 使用步骤2中@Import标注的类作为AnnotationConfigApplicationContext构造参数创建spring容器
4. 使用AnnotationConfigApplicationContext操作bean
这个接口提供了通过spring容器api的方式直接向容器中注册bean
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
}
}
importingClassMetadata:AnnotationMetadata类型的,通过这个可以获取被@Import注解标注的类所有注解的信息。
registry:BeanDefinitionRegistry类型,是一个接口,内部提供了注册bean的各种方法。
importBeanNameGenerator:BeanNameGenerator类型,是一个接口,内部有一个方法,用来生成bean的名称。
BeanDefinitionRegistry接口:bean定义注册器,提供了bean注册的各种方法,基本上所有bean工厂都实现了这个接口,让bean工厂拥有bean注册的各种能力,AnnotationConfigApplicationContext类也实现了这个接口
public interface BeanDefinitionRegistry extends AliasRegistry {
/**
* 注册一个新的bean定义
* beanName:bean的名称
* beanDefinition:bean定义信息
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
/**
* 通过bean名称移除已注册的bean
* beanName:bean名称
*/
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
/**
* 通过名称获取bean的定义信息
* beanName:bean名称
*/
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
/**
* 查看beanName是否注册过
*/
boolean containsBeanDefinition(String beanName);
/**
* 获取已经定义(注册)的bean名称列表
*/
String[] getBeanDefinitionNames();
/**
* 返回注册器中已注册的bean数量
*/
int getBeanDefinitionCount();
/**
* 确定给定的bean名称或者别名是否已在此注册表中使用
* beanName:可以是bean名称或者bean的别名
*/
boolean isBeanNameInUse(String beanName);
}
BeanNameGenerator接口:bean名称生成器
public interface BeanNameGenerator {
String generateBeanName(
BeanDefinition definition,
BeanDefinitionRegistry registry);
}
这个接口spring有三个内置的实现:
DefaultBeanNameGenerator:默认bean名称生成器,xml中bean未指定名称的时候,默认就会使用这个生成器,默认为:完整的类名#bean编号
AnnotationBeanNameGenerator:注解方式的bean名称生成器,比如通过@Component(bean名称)的方式指定bean名称,如果没有通过注解方式指定名称,默认会将完整的类名作为bean名称。
FullyQualifiedAnnotationBeanNameGenerator:将完整的类名作为bean的名称
BeanDefinition接口:bean定义信息,用来表示bean定义信息的接口,我们向容器中注册bean之前,会通过xml或者其他方式定义bean的各种配置信息,bean的所有配置信息都会被转换为一个BeanDefinition对象,然后通过容器中BeanDefinitionRegistry接口中的方法,将BeanDefinition注册到spring容器中,完成bean的注册操作。
案例:
Service1:
package com.javacode2018.lesson001.demo24.test4;
public class Service1 {}
Service2:需要注入Service1
package com.javacode2018.lesson001.demo24.test4;
public class Service2 {
private Service1 service1;
public Service1 getService1() { return service1;}
public void setService1(Service1 service1) { this.service1 = service1; }
@Override
public String toString() {
return "Service2{" + "service1=" + service1 +'}'; }
}
定义一个类实现ImportBeanDefinitionRegistrar接口,然后在里面实现上面2个类的注册
package com.javacode2018.lesson001.demo24.test4;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//定义一个bean:Service1
BeanDefinition service1BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service1.class).getBeanDefinition();
//注册bean
registry.registerBeanDefinition("service1", service1BeanDinition);
//定义一个bean:Service2,通过addPropertyReference注入service1
BeanDefinition service2BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service2.class).
addPropertyReference("service1", "service1").
getBeanDefinition();
//注册bean
registry.registerBeanDefinition("service2", service2BeanDinition);
}
}
定义配置类导入实现类
@Import({MyImportBeanDefinitionRegistrar.class})
public class ImportBean{}
测试:
@Test
public void test4() {
//1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportBean.class);
//2.输出容器中定义的所有bean信息
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
- value为ImportSelector接口类型
7. 定义ImportSelector接口实现类,在selectImports返回需要导入的类的名称数组
8. 使用@Import来导入步骤1中定义的类
9. 使用步骤2中@Import标注的类作为AnnotationConfigApplicationContext构造参数创建spring容器
10. 使用AnnotationConfigApplicationContext操作bean
ImportSelector接口:
public interface ImportSelector {
/**
* 返回需要导入的类名的数组,可以是任何普通类,配置类(@Configuration、@Bean、@CompontentScan等标注的类)
* @importingClassMetadata:用来获取被@Import标注的类上面所有的注解信息
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
案例:
普通类:Service1
package com.javacode2018.lesson001.demo24.test5;
public class Service1 {}
@Configuration标注的配置类:Module1Config
package com.javacode2018.lesson001.demo24.test5;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Module1Config {
@Bean
public String name() {
return "公众号:路人甲java";
}
@Bean
public String address() {
return "上海市";
}
}
自定义一个ImportSelector,然后返回上面2个类的名称
package com.javacode2018.lesson001.demo24.test5;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
Service1.class.getName(),
Module1Config.class.getName()
};
}
}
@Import标注的类,导入MyImportSelector
package com.javacode2018.lesson001.demo24.test5;
import com.lesson001.demo24.test4.MyImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Import;
/**
* 通过@Import导入MyImportSelector接口实现类
*/
@Import({MyImportSelector.class})
public class MainConfig5 {}
测试
@Test
public void test5() {
//1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
//2.输出容器中定义的所有bean信息
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
- value为DeferredImportSelector接口类型
DeferredImportSelector是ImportSelector的子接口,既然是ImportSelector的子接口,所以也可以通过@Import进行导入。和ImportSelector不同地方有两点:延迟导入和指定导入的类的处理顺序。
延迟导入:
@Import的value包含了多个普通类、多个@Configuration标注的配置类、多个ImportSelector接口的实现类,多个ImportBeanDefinitionRegistrar接口的实现类,还有DeferredImportSelector接口实现类,此时spring处理这些被导入的类的时候,会将DeferredImportSelector类型的放在最后处理,会先处理其他被导入的类,其他类会按照value所在的前后顺序进行处理
案例:
来3个配置类,每个配置类中都通过@Bean定一个string类型的bean,内部输出一句文字。
Configuration1:
package com.javacode2018.lesson001.demo24.test7;
@Configuration
public class Configuration1 {
@Bean
public String name1() {
System.out.println("name1");
return "name1";
}
}
Configuration2:
package com.javacode2018.lesson001.demo24.test7;
@Configuration
public class Configuration2 {
@Bean
public String name2() {
System.out.println("name2");
return "name2";
}
}
Configuration3:
package com.javacode2018.lesson001.demo24.test7;
@Configuration
public class Configuration3 {
@Bean
public String name3() {
System.out.println("name3");
return "name3";
}
}
一个ImportSelector实现类,导入Configuration1
package com.javacode2018.lesson001.demo24.test7;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class ImportSelector1 implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{ Configuration1.class.getName()};
}
}
一个DeferredImportSelector实现类,导入Configuration2
package com.javacode2018.lesson001.demo24.test7;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class DeferredImportSelector1 implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{Configuration2.class.getName()};
}
}
一个总的配置类
package com.javacode2018.lesson001.demo24.test7;
import org.springframework.context.annotation.Import;
@Import({
DeferredImportSelector1.class,
Configuration3.class,
ImportSelector1.class,
})
public class MainConfig7 {}
测试及输出:
@Test
public void test7() {
//1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);
}
name3
name1
name2
输出的结果结合一下@Import中被导入的3个类的顺序,可以看出DeferredImportSelector1是被最后处理的,其他2个是按照value中所在的先后顺序处理的。
指定导入的类的处理顺序:
@Import中有多个DeferredImportSelector接口的实现类时候,可以指定他们的顺序,指定顺序常见2种方式:
实现Ordered接口的方式:
org.springframework.core.Ordered
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
int getOrder();
}
实现Order注解的方式:
org.springframework.core.annotation.Order
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
int value() default Ordered.LOWEST_PRECEDENCE;
}
案例:
2个配置类,内部都有一个@Bean标注的方法,用来注册一个bean
Configuration1:
package com.javacode2018.lesson001.demo24.test8;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Configuration1 {
@Bean
public String name1() {
System.out.println("name1");
return "name1";
}
}
Configuration2:
package com.javacode2018.lesson001.demo24.test8;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Configuration2 {
@Bean
public String name2() {
System.out.println("name2");
return "name2";
}
}
来2个DeferredImportSelector实现类,分别来导入上面2个配置文件,顺便通过Ordered接口指定一下顺序,DeferredImportSelector1的order为2,DeferredImportSelector2的order为1,order值越小优先级越高
DeferredImportSelector1:
package com.javacode2018.lesson001.demo24.test8;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
public class DeferredImportSelector1 implements DeferredImportSelector, Ordered {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{Configuration1.class.getName()};
}
@Override
public int getOrder() {
return 2;
}
}
DeferredImportSelector2:
package com.javacode2018.lesson001.demo24.test8;
import com.javacode2018.lesson001.demo24.test7.Configuration2;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
public class DeferredImportSelector2 implements DeferredImportSelector, Ordered {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{Configuration2.class.getName()};
}
@Override
public int getOrder() {
return 1;
}
}
来个总的配置类,引入上面两个ImportSelector
MainConfig8:
package com.javacode2018.lesson001.demo24.test8
import org.springframework.context.annotation.Import;
@Import({
DeferredImportSelector1.class,
DeferredImportSelector2.class,
})
public class MainConfig8 {}
测试输出:
@Test
public void test8() {
//1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig8.class);
}
name2
name1
4.3.2.1 案列
需求:
凡是类名中包含service的,调用他们内部任何方法,我们希望调用之后能够输出这些方法的耗时
实现分析:
我们可以通过代理来实现,bean实例创建的过程中,我们可以给这些bean生成一个代理,在代理中统计方法的耗时,这里面有2点:
- 创建一个代理类,通过代理来间接访问需要统计耗时的bean对象
- 拦截bean的创建,给bean实例生成代理生成代理
具体实现:
Service1
package com.javacode2018.lesson001.demo24.test6;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
public void m1() {
System.out.println(this.getClass() + ".m1()");
}
}
Service2
package com.javacode2018.lesson001.demo24.test6;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
public void m1() {
System.out.println(this.getClass() + ".m1()");
}
}
创建统计耗时的代理类:用cglib来实现一个代理类
package com.javacode2018.lesson001.demo24.test6;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CostTimeProxy implements MethodInterceptor {
//目标对象
private Object target;
public CostTimeProxy(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long starTime = System.nanoTime();
//调用被代理对象(即target)的方法,获取结果
Object result = method.invoke(target, objects); //@1
long endTime = System.nanoTime();
System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
return result;
}
/**
* 创建任意类的代理对象
*
* @param target
* @param <T>
* @return
*/
public static <T> T createProxy(T target) {
CostTimeProxy costTimeProxy = new CostTimeProxy(target);
Enhancer enhancer = new Enhancer();
enhancer.setCallback(costTimeProxy);
enhancer.setSuperclass(target.getClass());
return (T) enhancer.create();
}
}
拦截bean实例的创建,返回代理对象
package com.javacode2018.lesson001.demo24.test6;
import com.javacode2018.lesson001.demo23.test4.CostTimeProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;
public class MethodCostTimeProxyBeanPostProcessor implements BeanPostProcessor {
@Nullable
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass().getName().toLowerCase().contains("service")) {
return CostTimeProxy.createProxy(bean); //@1
} else {
return bean;
}
}
}
需要将MethodCostTimeProxyBeanPostProcessor注册到容器中才会起作用,下面我们通过@Import结合ImportSelector的方式来导入这个类,将其注册到容器中
package com.javacode2018.lesson001.demo24.test6;
import com.javacode2018.lesson001.demo23.test4.MethodCostTimeProxyBeanPostProcessor;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MethodCostTimeImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MethodCostTimeProxyBeanPostProcessor.class.getName()};
}
}
来一个@Import来导入MethodCostTimeImportSelector,下面我们使用注解的方式,在注解上使用@Import
package com.javacode2018.lesson001.demo24.test6;
import com.javacode2018.lesson001.demo23.test4.MethodCostTimeImportSelector;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MethodCostTimeImportSelector.class)
public @interface EnableMethodCostTime {
}
来一个总的配置类
package com.javacode2018.lesson001.demo24.test6;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@EnableMethodCostTime //@1
public class MainConfig6 {
}
上面使用了@CompontentScan注解,此时会将Servce1和Service2这两个类注册到容器中。
@1:此处使用了@EnableMethodCostTime注解,而@EnableMethodCostTime注解上使用了@Import(MethodCostTimeImportSelector.class),此时MethodCostTimeImportSelector类中的MethodCostTimeProxyBeanPostProcessor会被注册到容器,会拦截bean的创建,创建耗时代理对象。
测试:
@Test
public void test6() {
//1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
Service1 service1 = context.getBean(Service1.class);
Service2 service2 = context.getBean(Service2.class);
service1.m1();
service2.m1();
}
结果:
class com.javacode2018.lesson001.demo24.test6.Service1.m1()
public void com.javacode2018.lesson001.demo24.test6.Service1.m1(),耗时(纳秒):74200
class com.javacode2018.lesson001.demo24.test6.Service2.m1()
public void com.javacode2018.lesson001.demo24.test6.Service2.m1(),耗时(纳秒):33800
如果我们不想开启方法耗时统计,只需要将MainConfig6上的@EnableMethodCostTime去掉就可以了
spring中有很多类似的注解,以@EnableXXX开头的注解,基本上都是通过上面这种方式实现的,如:
@EnableAspectJAutoProxy
@EnableCaching
@EnableAsync