() { @Override public Object getObject() throws BeansException { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } } }); // 返回对应的实例 bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, “Scope ‘“ + scopeName + “‘ is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton”, ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // 检查需要的类型是否符合bean的实际类型,对应getBean时指定的requireType if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) { try { // 执行类型转换,转换成期望的类型 return this.getTypeConverter().convertIfNecessary(bean, requiredType); } catch (TypeMismatchException ex) { if (logger.isDebugEnabled()) { logger.debug(“Failed to convert bean ‘“ + name + “‘ to required type ‘“ + ClassUtils.getQualifiedName(requiredType) + “‘“, ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean; }
4、BeanNameGenerator及其实现类
4.1、BeanNameGeneratorBeanNameGenerator接口位于 org.springframework.beans.factory.support 包下面: /* Strategy interface for generating bean names for bean definitions. @author Juergen Hoeller @since 2.0.3 / public interface BeanNameGenerator {
/**<br /> * Generate a bean name for the given bean definition.<br /> * @param definition the bean definition to generate a name for<br /> * @param registry the bean definition registry that the given definition<br /> * is supposed to be registered with<br /> * @return the generated bean name<br /> */ < br /> String generateBeanName ( BeanDefinition definition , BeanDefinitionRegistry registry );
} 它有两个实现类:分别是: 其中DefaultBeanNameGenerator是给资源文件加载bean时使用(BeanDefinitionReader中使用);AnnotationBeanNameGenerator是为了处理注解生成bean name的情况。
4.2、AnnotationBeanNameGenerator/* 此方法是接口中抽象方法的实现。 该方法分为两个部分: 第一个部分:当指定了bean的名称,则直接使用指定的名称。 第二个部分:当没有指定bean的名称时,则使用当前类的短类名作为bean的唯一标识。 / @Override public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { //判断bean的定义信息是否为基于注解的 if (definition instanceof AnnotatedBeanDefinition) { //解析注解中的属性,看看有没有指定的bean的唯一标识 String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); if (StringUtils.hasText(beanName)) { //返回注解的属性指定的bean的唯一标识 return beanName; } } // 调用方法,使用注解bean名称的命名规则,生成bean的唯一标识 return buildDefaultBeanName(definition, registry); }
/* Derive a default bean name from the given bean definition.
The default implementation delegates to {@link #buildDefaultBeanName(BeanDefinition)}. @param definition the bean definition to build a bean name for @param registry the registry that the given bean definition is being registered with @return the default bean name (never {@code null}) */ protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { return buildDefaultBeanName(definition); }
/ Derive a default bean name from the given bean definition.
The default implementation simply builds a decapitalized version of the short class name: e.g. “mypackage.MyJdbcDao” -> “myJdbcDao”.
Note that inner classes will thus have names of the form “outerClassName.InnerClassName”, which because of the period in the name may be an issue if you are autowiring by name. @param definition the bean definition to build a bean name for @return the default bean name (never {@code null}) */ protected String buildDefaultBeanName(BeanDefinition definition) { String beanClassName = definition.getBeanClassName(); Assert.state(beanClassName != null, “No bean class name set”); String shortClassName = ClassUtils.getShortName(beanClassName); return Introspector.decapitalize(shortClassName); } ClassUtils的代码节选: / Miscellaneous class utility methods. Mainly for internal use within the framework. @author Juergen Hoeller @author Keith Donald @author Rob Harrop @author Sam Brannen @since 1.1 @see TypeUtils @see ReflectionUtils */ public abstract class ClassUtils {
/** Suffix for array class names: {@code "[]"}. */ < br /> public static final String ARRAY_SUFFIX = "[]" ;
/** Prefix for internal array class names: {@code "["}. */ < br /> private static final String INTERNAL_ARRAY_PREFIX = "[" ;
/** Prefix for internal non-primitive array class names: {@code "[L"}. */ < br /> private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L" ;
/** The package separator character: {@code '.'}. */ < br /> private static final char PACKAGE_SEPARATOR = '.' ;
/** The path separator character: {@code '/'}. */ < br /> private static final char PATH_SEPARATOR = '/' ;
/** The inner class separator character: {@code '$'}. */ < br /> private static final char INNER_CLASS_SEPARATOR = '$' ;
/** The CGLIB class separator: {@code "$$"}. */ < br /> public static final String CGLIB_CLASS_SEPARATOR = "$$" ;
/** The ".class" file suffix. */ < br /> public static final String CLASS_FILE_SUFFIX = ".class" ;< br /> < br /> /**<br /> * Get the class name without the qualified package name.<br /> * @param className the className to get the short name for<br /> * @return the class name of the class without the package name<br /> * @throws IllegalArgumentException if the className is empty<br /> */ < br /> public static String getShortName ( String className ) {< br /> Assert . hasLength ( className , "Class name must not be empty" );< br /> int lastDotIndex = className . lastIndexOf ( PACKAGE_SEPARATOR );< br /> int nameEndIndex = className . indexOf ( CGLIB_CLASS_SEPARATOR );< br /> if ( nameEndIndex == - 1 ) {< br /> nameEndIndex = className . length ();< br /> }< br /> String shortName = className . substring ( lastDotIndex + 1 , nameEndIndex );< br /> shortName = shortName . replace ( INNER_CLASS_SEPARATOR , PACKAGE_SEPARATOR );< br /> return shortName ;< br /> }< br /> < br /> //其余代码略<br />}
4.3、DefaultBeanNameGenerator/* Default implementation of the {@link BeanNameGenerator} interface, delegating to {@link BeanDefinitionReaderUtils#generateBeanName(BeanDefinition, BeanDefinitionRegistry)}. @author Juergen Hoeller @since 2.0.3 */ public class DefaultBeanNameGenerator implements BeanNameGenerator {
@Override < br /> public String generateBeanName ( BeanDefinition definition , BeanDefinitionRegistry registry ) {< br /> return BeanDefinitionReaderUtils . generateBeanName ( definition , registry );< br /> }< br />}< br /> DefaultBeanNameGenerator 类将具体的处理方式委托给了, BeanDefinitionReaderUtils #generateBeanName(BeanDefinition, BeanDefinitionRegistry)方法处理。
以下是代码节选: public abstract class BeanDefinitionReaderUtils {
public static String generateBeanName ( BeanDefinition beanDefinition , BeanDefinitionRegistry registry )< br /> throws BeanDefinitionStoreException {< br /> //此方法除了bean的定义信息和定义注册之外,还有一个布尔类型的值,用于确定是内部bean还是顶层bean<br /> return generateBeanName(beanDefinition, registry, false);<br /> }
/**<br /> * 生成bean的唯一标识(默认规则)<br /> */ < br /> public static String generateBeanName (< br /> BeanDefinition definition , BeanDefinitionRegistry registry , boolean isInnerBean )< br /> throws BeanDefinitionStoreException {
String generatedBeanName = definition . getBeanClassName ();< br /> if ( generatedBeanName == null ) {< br /> if ( definition . getParentName () != null ) {< br /> generatedBeanName = definition . getParentName () + "$child" ;< br /> }< br /> else if ( definition . getFactoryBeanName () != null ) {< br /> generatedBeanName = definition . getFactoryBeanName () + "$created" ;< br /> }< br /> }< br /> if (! StringUtils . hasText ( generatedBeanName )) {< br /> throw new BeanDefinitionStoreException ( "Unnamed bean definition specifies neither " +< br /> "'class' nor 'parent' nor 'factory-bean' - can't generate bean name" );< br /> }
String id = generatedBeanName ;< br /> if ( isInnerBean ) {< br /> // Inner bean: generate identity hashcode suffix.<br /> id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);<br /> }<br /> else {<br /> // Top-level bean: use plain class name with unique suffix if necessary.<br /> return uniqueBeanName(generatedBeanName, registry);<br /> }<br /> return id;<br /> }
//其他代码略<br />}
5、ScopedProxyMode枚举/* Enumerates the various scoped-proxy options.
For a more complete discussion of exactly what a scoped proxy is, see the section of the Spring reference documentation entitled ‘Scoped beans as dependencies ‘. @author Mark Fisher @since 2.5 @see ScopeMetadata */ public enum ScopedProxyMode {
/**<br /> * Default typically equals {@link #NO}, unless a different default<br /> * has been configured at the component-scan instruction level.<br /> */ < br /> DEFAULT ,
/**<br /> * Do not create a scoped proxy.<br /> * <p>This proxy-mode is not typically useful when used with a<br /> * non-singleton scoped instance, which should favor the use of the<br /> * {@link #INTERFACES} or {@link #TARGET_CLASS} proxy-modes instead if it<br /> * is to be used as a dependency.<br /> */ < br /> NO ,
/**<br /> * Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by<br /> * the class of the target object.<br /> */ < br /> INTERFACES ,
/**<br /> * Create a class-based proxy (uses CGLIB).<br /> */ < br /> TARGET_CLASS ;
}
6、自定义组件扫描过滤规则
6.1、FilterType枚举public enum FilterType {
/**<br /> * Filter candidates marked with a given annotation.<br /> * @see org.springframework.core.type.filter.AnnotationTypeFilter<br /> */ < br /> ANNOTATION ,
/**<br /> * Filter candidates assignable to a given type.<br /> * @see org.springframework.core.type.filter.AssignableTypeFilter<br /> */ < br /> ASSIGNABLE_TYPE ,
/**<br /> * Filter candidates matching a given AspectJ type pattern expression.<br /> * @see org.springframework.core.type.filter.AspectJTypeFilter<br /> */ < br /> ASPECTJ ,
/**<br /> * Filter candidates matching a given regex pattern.<br /> * @see org.springframework.core.type.filter.RegexPatternTypeFilter<br /> */ < br /> REGEX ,
/** Filter candidates using a given custom<br /> * {@link org.springframework.core.type.filter.TypeFilter} implementation.<br /> */ < br /> CUSTOM
}
6.2、TypeFilter接口@FunctionalInterface public interface TypeFilter {
/**<br /> * 此方法返回一个boolean类型的值。<br /> * 当返回true时,表示加入到spring的容器中。返回false时,不加入容器。<br /> * 参数metadataReader:表示读取到的当前正在扫描的类的信息<br /> * 参数metadataReaderFactory:表示可以获得到其他任何类的信息<br /> */ < br /> boolean match ( MetadataReader metadataReader , MetadataReaderFactory metadataReaderFactory )< br /> throws IOException ;
}
6.3、使用Spring提供的过滤规则-AnnotationTypeFilter/* @author 黑马程序员 @Company http://www.itheima.com 当我们使用注解驱动开发JavaEE项目时,spring提供的容器分为RootApplicationContext和 ServletApplicationContext。此时我们不希望Root容器创建时把Controller加入到容器中, 就可以使用过滤规则排除@Controller注解配置的Bean对象。 */ @Configuration @ComponentScan(value = “com.itheima”,excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)) public class SpringConfiguration { }
6.4、自定义过滤规则
6.4.1、场景分析在实际开发中,有很多下面这种业务场景:一个业务需求根据环境的不同可能会有很多种实现。针对不同的环境,要加载不同的实现。我们看下面这个案例:< br /> 我们现在是一个汽车销售集团,在成立之初,只是在北京销售汽车,我们的项目研发完成后只在北京部署上线。但随着公司的业务发展,现在全国各地均有销售大区,总部设在北京。各大区有独立的项目部署,但是每个大区的业绩计算和绩效提成的计算方式并不相同。< br /> 例如:< br /> 在华北区销售一台豪华级轿车绩效算 5 ,提成销售额 1 %,销售豪华级 SUV 绩效算 3 ,提成是 0.5 %。< br /> 在西南区销售一台豪华级轿车绩效算 3 ,提成销售额 0.5 %,销售豪华级 SUV 绩效算 5 ,提成是 1.5 %。< br /> 这时,我们如果针对不同大区对项目源码进行删减替换,会带来很多不必要的麻烦。而如果加入一些 if / else 的判断,显然过于简单粗暴。此时应该考虑采用桥接设计模式,把将涉及到区域性差异的模块功能单独抽取到代表区域功能的接口中。针对不同区域进行实现。并且在扫描组件注册到容器中时,采用哪个区域的具体实现,应该采用配置文件配置起来。而自定义 TypeFilter 就可以实现注册指定区域的组件到容器中。
6.4.2、代码实现/* 区域的注解 @author 黑马程序员 @Company http://www.itheima.com */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface District {
/**<br /> * 指定区域的名称<br /> * @return<br /> */ < br /> String value () default "" ;< br />}
/* 销售分成的桥接接口 @author 黑马程序员 @Company http://www.itheima.com */ public interface DistrictPercentage {
/**<br /> * 不同车型提成<br /> * @param carType<br /> */ < br /> void salePercentage ( String carType );< br />}
/* 绩效计算桥接接口 @author 黑马程序员 @Company http://www.itheima.com */ public interface DistrictPerformance {
/**<br /> * 计算绩效<br /> * @param carType<br /> */ < br /> void calcPerformance ( String carType );< br />}
/* 华北区销售分成具体实现 @author 黑马程序员 @Company http://www.itheima.com */ @Component(“districtPercentage”) @District(“north”) public class NorthDistrictPercentage implements DistrictPercentage {
@Override < br /> public void salePercentage ( String carType ) {< br /> if ( "SUV" . equalsIgnoreCase ( carType )) {< br /> System . out . println ( "华北区" + carType + "提成1%" );< br /> } else if ( "car" . equalsIgnoreCase ( carType )){< br /> System . out . println ( "华北区" + carType + "提成0.5%" );< br /> }< br /> }< br />}
/* 华北区销售绩效具体实现 @author 黑马程序员 @Company http://www.itheima.com */ @Component(“districtPerformance”) @District(“north”) public class NorthDistrictPerformance implements DistrictPerformance {
@Override < br /> public void calcPerformance ( String carType ) {< br /> if ( "SUV" . equalsIgnoreCase ( carType )) {< br /> System . out . println ( "华北区" + carType + "绩效3" );< br /> } else if ( "car" . equalsIgnoreCase ( carType )){< br /> System . out . println ( "华北区" + carType + "绩效5" );< br /> }< br /> }< br />}
/* 西南区销售分成具体实现 @author 黑马程序员 @Company http://www.itheima.com */ @Component(“districtPercentage”) @District(“southwest”) public class SouthwestDistrictPercentage implements DistrictPercentage {
@Override < br /> public void salePercentage ( String carType ) {< br /> if ( "SUV" . equalsIgnoreCase ( carType )) {< br /> System . out . println ( "西南区" + carType + "提成1.5%" );< br /> } else if ( "car" . equalsIgnoreCase ( carType )){< br /> System . out . println ( "西南区" + carType + "提成0.5%" );< br /> }< br /> }< br />}
/* 西南区绩效计算具体实现 @author 黑马程序员 @Company http://www.itheima.com */ @Component(“districtPerformance”) @District(“southwest”) public class SouthwestDistrictPerformance implements DistrictPerformance {
@Override < br /> public void calcPerformance ( String carType ) {< br /> if ( "SUV" . equalsIgnoreCase ( carType )) {< br /> System . out . println ( "西南区" + carType + "绩效5" );< br /> } else if ( "car" . equalsIgnoreCase ( carType )){< br /> System . out . println ( "西南区" + carType + "绩效3" );< br /> }< br /> }< br />}
/ spring的配置类 用于替代xml配置 @author 黑马程序员 @Company http://www.itheima.com */ @Configuration @PropertySource(value = “classpath:district.properties”) @ComponentScan(value = “com.itheima”, excludeFilters = @ComponentScan.Filter(type=FilterType.CUSTOM,classes = DistrictTypeFilter.class)) public class SpringConfiguration { } / spring的自定义扫描规则 @author 黑马程序员 @Company http://www.itheima.com / public class DistrictTypeFilter extends AbstractTypeHierarchyTraversingFilter {
//定义路径校验类对象<br /> private PathMatcher pathMatcher;
//注意:使用@Value注解的方式是获取不到配置值的。<br /> //因为Spring的生命周期里,负责填充属性值的InstantiationAwareBeanPostProcessor 与TypeFilter的实例化过程压根搭不上边。<br />// @Value("${common.district.name}")<br /> private String districtName;
/**<br /> * 默认构造函数<br /> */ < br /> public DistrictTypeFilter () {< br /> //1.第一个参数:不考虑基类。2.第二个参数:不考虑接口上的信息<br /> super(false, false);
//借助Spring默认的Resource通配符路径方式<br /> pathMatcher = new AntPathMatcher();
//硬编码读取配置信息<br /> try {<br /> Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("district.properties");<br /> districtName = loadAllProperties.getProperty("common.district.name");<br /> } catch (IOException e) {<br /> // TODO Auto-generated catch block<br /> e.printStackTrace();<br /> }<br /> }
//注意本类将注册为Exclude, 返回true代表拒绝<br /> @Override<br /> protected boolean matchClassName(String className) {<br /> try{<br /> if (!isPotentialPackageClass(className)) {<br /> return false;<br /> }
// 判断当前区域是否和所配置的区域一致, 不一致则阻止载入Spring容器<br /> Class<?> clazz = ClassUtils.forName(className, DistrictTypeFilter.class.getClassLoader());<br /> District districtAnnotation = clazz.getAnnotation(District.class);<br /> if(null == districtAnnotation){<br /> return false;<br /> }<br /> final String districtValue = districtAnnotation.value();<br /> return (!districtName.equalsIgnoreCase(districtValue));<br /> }catch (Exception e){<br /> throw new RuntimeException(e);<br /> }<br /> }
// 潜在的满足条件的类的类名, 指定package下<br /> private static final String PATTERN_STANDARD = ClassUtils<br /> .convertClassNameToResourcePath("com.itheima.service.impl.*");
// 本类逻辑中可以处理的类 -- 指定package下的才会进行逻辑判断,<br /> private boolean isPotentialPackageClass(String className) {<br /> // 将类名转换为资源路径, 以进行匹配测试<br /> final String path = ClassUtils.convertClassNameToResourcePath(className);<br /> return pathMatcher.match(PATTERN_STANDARD, path);<br /> }
} /* @author 黑马程序员 @Company http://www.itheima.com / public class SpringAnnotationTypeFilterTest {
public static void main ( String [] args ) {< br /> AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext ( "config" );< br /> DistrictPerformance districtPerformance = ac . getBean ( "districtPerformance" , DistrictPerformance . class );< br /> districtPerformance . calcPerformance ( "SUV" );
DistrictPercentage districtPercentage = ac . getBean ( "districtPercentage" , DistrictPercentage . class );< br /> districtPercentage . salePercentage ( "car" );
}< br />}
7、@Import注解的高级分析
7.1、ImportSelector和ImportBeanDefinitionRegistrar介绍特别说明: 我们在注入bean对象时,可选的方式有很多种。 例如: 我们自己写的类,可以使用@Component,@Service,@Repository,@Controller等等。 我们导入的第三方库中的类,可以使用@Bean(当需要做一些初始化操作时,比如DataSource),也可以使用@Import注解,直接指定要引入的类的字节码。 但是当我们的类很多时,在每个类上加注解会很繁琐,同时使用@Bean或者@Import写起来也很麻烦。此时我们就可以采用自定义ImportSelector或者ImportBeanDefinitionRegistrar来实现。顺便说一句,在SpringBoot中,@EnableXXX这样的注解,绝大多数都借助了ImportSelector或者ImportBeanDefinitionRegistrar。在我们的spring中,@EnableTransactionManagement就是借助了ImportSelector,而@EnableAspectJAutoporxy就是借助了ImportBeanDefinitionRegistrar。 共同点: 他们都是用于动态注册bean对象到容器中的。并且支持大批量的bean导入。 区别: ImportSelector是一个接口,我们在使用时需要自己提供实现类。实现类中返回要注册的bean的全限定类名数组,然后执行ConfigurationClassParser类中中的processImports方法注册bean对象的。 ImportBeanDefinitionRegistrar也是一个接口,需要我们自己编写实现类,在实现类中手动注册bean到容器中。
注意事项: 实现了ImportSelector接口或者ImportBeanDefinitionRegistrar接口的类不会被解析成一个Bean注册到容器中。 同时,在注册到容器中时bean的唯一标识是全限定类名,而非短类名。
7.2、自定义ImportSelector/* @author 黑马程序员 @Company http://www.itheima.com / public interface UserService { void saveUser(); }
/* @author 黑马程序员 @Company http://www.itheima.com / public class UserServiceImpl implements UserService {
@Override < br /> public void saveUser () {< br /> System . out . println ( "保存用户" );< br /> }< br />}
/ @author 黑马程序员 @Company http://www.itheima.com */ @Configuration @ComponentScan(“com.itheima”) @Import(CustomeImportSelector.class) public class SpringConfiguration { } / customeimport.properties配置文件中的内容: custome.importselector.expression= com.itheima.service.impl. @author 黑马程序员 @Company http://www.itheima.com / public class CustomeImportSelector implements ImportSelector {
private String expression ;
public CustomeImportSelector (){< br /> try {< br /> Properties loadAllProperties = PropertiesLoaderUtils . loadAllProperties ( "customeimport.properties" );< br /> expression = loadAllProperties . getProperty ( "custome.importselector.expression" );< br /> } catch ( IOException e ) {< br /> // TODO Auto-generated catch block<br /> e.printStackTrace();<br /> }<br /> }
/**<br /> * 生成要导入的bean全限定类名数组<br /> * @param importingClassMetadata<br /> * @return<br /> */ < br /> @Override < br /> public String [] selectImports ( AnnotationMetadata importingClassMetadata ) {< br /> //1.定义扫描包的名称<br /> String[] basePackages = null;<br /> //2.判断有@Import注解的类上是否有@ComponentScan注解<br /> if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {<br /> //3.取出@ComponentScan注解的属性<br /> Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());<br /> //4.取出属性名称为basePackages属性的值<br /> basePackages = (String[]) annotationAttributes.get("basePackages");<br /> }<br /> //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)<br /> if (basePackages == null || basePackages.length == 0) {<br /> String basePackage = null;<br /> try {<br /> //6.取出包含@Import注解类的包名<br /> basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();<br /> } catch (ClassNotFoundException e) {<br /> e.printStackTrace();<br /> }<br /> //7.存入数组中<br /> basePackages = new String[] {basePackage};<br /> }<br /> //8.创建类路径扫描器<br /> ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);<br /> //9.创建类型过滤器(此处使用切入点表达式类型过滤器)<br /> TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());<br /> //10.给扫描器加入类型过滤器<br /> scanner.addIncludeFilter(typeFilter);<br /> //11.创建存放全限定类名的集合<br /> Set<String> classes = new HashSet<>();<br /> //12.填充集合数据<br /> for (String basePackage : basePackages) {<br /> scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));<br /> }<br /> //13.按照规则返回<br /> return classes.toArray(new String[classes.size()]);<br /> }<br />}
/* 测试类 @author 黑马程序员 @Company http://www.itheima.com */ public class SpringCustomeImportSelectorTest {
public static void main ( String [] args ) {< br /> AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext ( "config" );< br /> String [] names = ac . getBeanDefinitionNames ();< br /> for ( String beanName : names ){< br /> Object obj = ac . getBean ( beanName );< br /> System . out . println ( beanName + "============" + obj );< br /> }< br /> }< br />}
7.3、自定义ImportBeanDefinitionRegistrar借助7.2小节的案例代码,只需要把配置改一下: /* @author 黑马程序员 @Company http://www.itheima.com / @Configuration @ComponentScan(“com.itheima”) @Import(CustomeImportDefinitionRegistrar.class) public class SpringConfiguration { }
/* 自定义bean导入注册器 @author 黑马程序员 @Company http://www.itheima.com */ public class CustomeImportDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
private String expression ;
public CustomeImportDefinitionRegistrar (){< br /> try {< br /> Properties loadAllProperties = PropertiesLoaderUtils . loadAllProperties ( "customeimport.properties" );< br /> expression = loadAllProperties . getProperty ( "custome.importselector.expression" );< br /> } catch ( IOException e ) {< br /> // TODO Auto-generated catch block<br /> e.printStackTrace();<br /> }<br /> }
@Override < br /> public void registerBeanDefinitions ( AnnotationMetadata importingClassMetadata , BeanDefinitionRegistry registry ) {< br /> //1.定义扫描包的名称<br /> String[] basePackages = null;<br /> //2.判断有@Import注解的类上是否有@ComponentScan注解<br /> if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {<br /> //3.取出@ComponentScan注解的属性<br /> Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());<br /> //4.取出属性名称为basePackages属性的值<br /> basePackages = (String[]) annotationAttributes.get("basePackages");<br /> }<br /> //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)<br /> if (basePackages == null || basePackages.length == 0) {<br /> String basePackage = null;<br /> try {<br /> //6.取出包含@Import注解类的包名<br /> basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();<br /> } catch (ClassNotFoundException e) {<br /> e.printStackTrace();<br /> }<br /> //7.存入数组中<br /> basePackages = new String[] {basePackage};<br /> }<br /> //8.创建类路径扫描器<br /> ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);<br /> //9.创建类型过滤器(此处使用切入点表达式类型过滤器)<br /> TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());<br /> //10.给扫描器加入类型过滤器<br /> scanner.addIncludeFilter(typeFilter);<br /> //11.扫描指定包<br /> scanner.scan(basePackages);<br /> }<br />}
7.4、原理分析我们写的自定义导入器的解析写在了ConfigurationClassParser类中的processImports方法,以下是源码节选: private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates, boolean checkForCircularImports) {
if ( importCandidates . isEmpty ()) {< br /> return ;< br /> }
if ( checkForCircularImports && isChainedImportOnStack ( configClass )) {< br /> this . problemReporter . error ( new CircularImportProblem ( configClass , this . importStack ));< br /> }< br /> else {< br /> this . importStack . push ( configClass );< br /> try {< br /> for ( SourceClass candidate : importCandidates ) {< br /> //对ImportSelector的处理<br /> if (candidate.isAssignable(ImportSelector.class)) {<br /> // Candidate class is an ImportSelector -> delegate to it to determine imports<br /> Class<?> candidateClass = candidate.loadClass();<br /> ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);<br /> ParserStrategyUtils.invokeAwareMethods(<br /> selector, this.environment, this.resourceLoader, this.registry);<br /> if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {<br /> //如果为延迟导入处理则加入集合当中<br /> this.deferredImportSelectors.add(<br /> new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));<br /> }<br /> else {<br /> //根据ImportSelector方法的返回值来进行递归操作<br /> String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());<br /> Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);<br /> processImports(configClass, currentSourceClass, importSourceClasses, false);<br /> }<br /> }<br /> else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {<br /> // Candidate class is an ImportBeanDefinitionRegistrar -><br /> // delegate to it to register additional bean definitions<br /> Class<?> candidateClass = candidate.loadClass();<br /> ImportBeanDefinitionRegistrar registrar =<br /> BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);<br /> ParserStrategyUtils.invokeAwareMethods(<br /> registrar, this.environment, this.resourceLoader, this.registry);<br /> configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());<br /> }<br /> else {<br /> // 如果当前的类既不是ImportSelector也不是ImportBeanDefinitionRegistar就进行@Configuration的解析处理<br /> // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -><br /> // process it as an @Configuration class<br /> this.importStack.registerImport(<br /> currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());<br /> processConfigurationClass(candidate.asConfigClass(configClass));<br /> }<br /> }<br /> }<br /> catch (BeanDefinitionStoreException ex) {<br /> throw ex;<br /> }<br /> catch (Throwable ex) {<br /> throw new BeanDefinitionStoreException(<br /> "Failed to process import candidates for configuration class [" +<br /> configClass.getMetadata().getClassName() + "]", ex);<br /> }<br /> finally {<br /> this.importStack.pop();<br /> }<br /> }<br /> }
8、自定义PropertySourceFactory实现YAML文件解析
8.1、PropertySourceFactory及DefaultPropertySourceFactory/* Strategy interface for creating resource-based {@link PropertySource} wrappers. @author Juergen Hoeller @since 4.3 @see DefaultPropertySourceFactory */ public interface PropertySourceFactory {
/**<br /> * Create a {@link PropertySource} that wraps the given resource.<br /> * @param name the name of the property source<br /> * @param resource the resource (potentially encoded) to wrap<br /> * @return the new {@link PropertySource} (never {@code null})<br /> * @throws IOException if resource resolution failed<br /> */ < br /> PropertySource <?> createPropertySource ( @Nullable String name , EncodedResource resource ) throws IOException ;
} /* The default implementation for {@link PropertySourceFactory}, wrapping every resource in a {@link ResourcePropertySource}. @author Juergen Hoeller @since 4.3 @see PropertySourceFactory @see ResourcePropertySource */ public class DefaultPropertySourceFactory implements PropertySourceFactory {
@Override < br /> public PropertySource <?> createPropertySource ( @Nullable String name , EncodedResource resource ) throws IOException {< br /> return ( name != null ? new ResourcePropertySource ( name , resource ) : new ResourcePropertySource ( resource ));< br /> }
}
8.2、执行过程分析
8.2.1、ResourcePropertySource/* DefaultPropertySourceFactory在创建PropertySource对象时使用的是此类的构造函数 在构造时,调用的 / public class ResourcePropertySource extends PropertiesPropertySource {
/**<br /> * 当我们没有指定名称时,执行的是此构造函数,此构造函数调用的是父类的构造函数<br /> * 通过读取父类的构造函数,得知第二个参数是一个properties文件<br /> * spring使用了一个工具类获取properties文件<br /> */ < br /> public ResourcePropertySource ( EncodedResource resource ) throws IOException {< br /> super ( getNameForResource ( resource . getResource ()), PropertiesLoaderUtils . loadProperties ( resource ));< br /> this . resourceName = null ;< br /> }< br /> < br /> //其他方法略<br />}
8.2.2、PropertiesPropertySource / 此类是ResourcePropertySource的父类 / public class PropertiesPropertySource extends MapPropertySource { / 此构造函数中包含两个参数:第一个是名称。第二个是解析好的properties文件 / @SuppressWarnings({“unchecked”, “rawtypes”}) public PropertiesPropertySource(String name, Properties source) { super(name, (Map) source); }
protected PropertiesPropertySource ( String name , Map < String , Object > source ) {< br /> super ( name , source );< br /> }
}
8.2.3、PropertiesLoaderUtils/* 获取properties的工具类 */ public abstract class PropertiesLoaderUtils {
private static final String XML_FILE_EXTENSION = ".xml" ;
/**<br /> * ResourcePropertySource类中的构造函数就是执行了此方法<br /> */ < br /> public static Properties loadProperties ( EncodedResource resource ) throws IOException {< br /> Properties props = new Properties ();< br /> fillProperties ( props , resource );< br /> return props ;< br /> }
/**<br /> * <br /> */ < br /> public static void fillProperties ( Properties props , EncodedResource resource )< br /> throws IOException {
fillProperties ( props , resource , new DefaultPropertiesPersister ());< br /> }
/**<br /> * 通过此方法源码,可以看出spring在使用@PropertySource注解时,支持xml和properties文件<br /> */ < br /> static void fillProperties ( Properties props , EncodedResource resource , PropertiesPersister persister )< br /> throws IOException {
InputStream stream = null ;< br /> Reader reader = null ;< br /> try {< br /> String filename = resource . getResource (). getFilename ();< br /> if ( filename != null && filename . endsWith ( XML_FILE_EXTENSION )) {< br /> stream = resource . getInputStream ();< br /> //读取xml文件<br /> persister.loadFromXml(props, stream);<br /> }<br /> else if (resource.requiresReader()) {<br /> reader = resource.getReader();<br /> //读取properties文件<br /> persister.load(props, reader);<br /> }<br /> else {<br /> stream = resource.getInputStream();<br /> persister.load(props, stream);<br /> }<br /> }<br /> finally {<br /> if (stream != null) {<br /> stream.close();<br /> }<br /> if (reader != null) {<br /> reader.close();<br /> }<br /> }<br /> }<br /> //其他代码略<br />}
8.2.4、PropertiesPersister /* spring中声明的解析xml和properties文件的接口 */ public interface PropertiesPersister {
<br /> /** <br /> * Load properties from the given Reader into the given <br /> * Properties object. <br /> * @param props the Properties object to load into <br /> * @param reader the Reader to load from <br /> * @throws IOException in case of I/O errors <br /> */ <br /> void load(Properties props, Reader reader) throws IOException;
/** <br /> * Load properties from the given XML InputStream into the <br /> * given Properties object. <br /> * @param props the Properties object to load into <br /> * @param is the InputStream to load from <br /> * @throws IOException in case of I/O errors <br /> * @see java.util.Properties#loadFromXML(java.io.InputStream) <br /> */ <br /> void loadFromXml(Properties props, InputStream is) throws IOException; <br /> <br /> //其他代码略 <br /> } <br /> /** <br /> * PropertiesPersister接口的实现类 <br /> */ <br /> public class DefaultPropertiesPersister implements PropertiesPersister {
@Override <br /> public void load(Properties props, Reader reader) throws IOException { <br /> props.load(reader);
@Override <br /> public void loadFromXml(Properties props, InputStream is) throws IOException { <br /> props.loadFromXML(is); <br /> }
//其他代码略 <br /> }
8.3、自定义PropertySourceFactory我们通过分析@PropertySource源码,得知默认情况下此注解只能解析properties文件和xml文件,而遇到yaml(yml)文件,解析就会报错。此时就需要我们自己编写一个PropertySourceFactory的实现类,借助yaml解析器,实现yml文件的解析。
8.3.1、编写yml配置文件jdbc: driver: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/spring_day01 username: root password: 1234
8.3.2、导入yaml解析器的坐标 org.yaml snakeyaml 1.23
8.3.3、编写自定义PropertySourceFactory/* 自定义资源文件解析器,用于解析yaml yml文件 @author 黑马程序员 @Company http://www.itheima.com */ public class CustomerPropertySourceFactory implements PropertySourceFactory {
/**<br /> * 重写接口中的方法<br /> * @param name<br /> * @param resource<br /> * @return<br /> * @throws IOException<br /> */ < br /> @Override < br /> public PropertySource <?> createPropertySource ( String name , EncodedResource resource ) throws IOException {< br /> //1.创建yaml文件解析工厂<br /> YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();<br /> //2.设置资源内容<br /> yaml.setResources(resource.getResource());<br /> //3.解析成properties文件<br /> Properties properties = yaml.getObject();<br /> //4.返回符合spring的PropertySource对象<br /> return name != null ? new PropertiesPropertySource(name,properties) : new PropertiesPropertySource(resource.getResource().getFilename(), properties);
}< br />}
8.3.4、使用@PropertyeSource的factory属性配置自定义工厂/ @author 黑马程序员 @Company http://www.itheima.com */ @Configuration @Import(JdbcConfig.class) @PropertySource(value = “classpath:jdbc.yml”,factory = CustomerPropertySourceFactory.class) @ComponentScan(“com.itheima”) public class SpringConfiguration { } / 连接数据库的配置 @author 黑马程序员 @Company http://www.itheima.com / public class JdbcConfig {
@Value ( "${jdbc.driver}" )< br /> private String driver ;< br /> @Value ( "${jdbc.url}" )< br /> private String url ;< br /> @Value ( "${jdbc.username}" )< br /> private String username ;< br /> @Value ( "${jdbc.password}" )< br /> private String password ;
@Bean ( "jdbcTemplate" )< br /> public JdbcTemplate createJdbcTemplate ( DataSource dataSource ){< br /> return new JdbcTemplate ( dataSource );< br /> }
@Bean ( "dataSource" )< br /> public DataSource createDataSource (){< br /> System . out . println ( driver );< br /> DriverManagerDataSource dataSource = new DriverManagerDataSource ();< br /> dataSource . setDriverClassName ( driver );< br /> dataSource . setUrl ( url );< br /> dataSource . setUsername ( username );< br /> dataSource . setPassword ( password );< br /> return dataSource ;< br /> }< br />}
8.3.5、测试运行结果/* @author 黑马程序员 @Company http://www.itheima.com / public class SpringAnnotationDrivenTest {
/**<br /> * 测试<br /> * @param args<br /> */ < br /> public static void main ( String [] args ) {< br /> ApplicationContext ac = new AnnotationConfigApplicationContext ( "config" );< br /> }< br />}
9、@Profile注解的使用
9.1、使用场景分析@Profile 注解是 spring 提供的一个用来标明当前运行环境的注解。我们正常开发的过程中经常遇到的问题是,开发环境是一套环境,测试是一套环境,线上部署又是一套环境。这样从开发到测试再到部署,会对程序中的配置修改多次,尤其是从测试到上线这个环节,让测试的也不敢保证改了哪个配置之后能不能在线上运行。为了解决上面的问题,我们一般会使用一种方法,就是针对不同的环境进行不同的配置,从而在不同的场景中跑我们的程序。< br /> 而 spring 中的 @Profile 注解的作用就体现在这里。在 spring 使用 DI 来注入的时候,能够根据当前制定的运行环境来注入相应的 bean 。最常见的就是使用不同的 DataSource 了。
9.2、代码实现
9.2.1、自定义不同环境的数据源package config;
import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile;
/* @author 黑马程序员 @Company http://www.itheima.com / public class JdbcConfig {
@Value ( "${jdbc.driver}" )< br /> private String driver ;< br /> @Value ( "${jdbc.url}" )< br /> private String url ;< br /> @Value ( "${jdbc.username}" )< br /> private String username ;< br /> @Value ( "${jdbc.password}" )< br /> private String password ;
/**<br /> * @return<br /> */ < br /> @Bean ( "dataSource" )< br /> @Profile ( "dev" )< br /> public DruidDataSource createDevDataSource (){< br /> DruidDataSource dataSource = new DruidDataSource ();< br /> dataSource . setDriverClassName ( driver );< br /> dataSource . setUrl ( url );< br /> dataSource . setUsername ( username );< br /> dataSource . setPassword ( password );< br /> dataSource . setMaxActive ( 10 );< br /> return dataSource ;< br /> }
/**<br /> * @return<br /> */ < br /> @Bean ( "dataSource" )< br /> @Profile ( "test" )< br /> public DruidDataSource createTestDataSource (){< br /> DruidDataSource dataSource = new DruidDataSource ();< br /> dataSource . setDriverClassName ( driver );< br /> dataSource . setUrl ( url );< br /> dataSource . setUsername ( username );< br /> dataSource . setPassword ( password );< br /> dataSource . setMaxActive ( 50 );< br /> return dataSource ;< br /> }
/**<br /> * @return<br /> */ < br /> @Bean ( "dataSource" )< br /> @Profile ( "produce" )< br /> public DruidDataSource createProduceDataSource (){< br /> DruidDataSource dataSource = new DruidDataSource ();< br /> dataSource . setDriverClassName ( driver );< br /> dataSource . setUrl ( url );< br /> dataSource . setUsername ( username );< br /> dataSource . setPassword ( password );< br /> dataSource . setMaxActive ( 100 );< br /> return dataSource ;< br /> }< br />}
9.2.2、编写配置类/* @author 黑马程序员 @Company http://www.itheima.com / @Configuration @Import(JdbcConfig.class) public class SpringConfiguration { }
9.2.3、编写测试类/* @author 黑马程序员 @Company http://www.itheima.com / @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) @ActiveProfiles(“test”) public class SpringProfileTest {
@Autowired < br /> private DruidDataSource druidDataSource ;
@Test < br /> public void testProfile (){< br /> System . out . println ( druidDataSource . getMaxActive ());< br /> }< br />}
9.2.4、测试结果