下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

Java中的Spring系列Bean装配的高级技术

作者:课课家教育     来源: http://www.kokojia.com点击数:631发布时间: 2018-11-21 10:07:05

标签: javaSE编程学习网络编程

大神带你学编程,欢迎选课

  spring系列(三) Bean装配的高级技术。

     今天与大家一起学习一下java 中Spring系列中的Bean高级技术装配

     profile

  不同于maven的profile, spring的profile不需要重新打包, 同一个版本的包文件可以部署在不同环境的服务器上, 只需要激活对应的profile就可以切换到对应的环境.

  @Profile({"test","dev"}) Java Config 通过这个注解指定bean属于哪个或哪些profile. 参数value是一个profile的字符串数组. 此注解可以添加到类或方法上. XML Config 对应的节点是beans的属性profile="dev", 可以在根节点下嵌套定义分属不同profile的节点, 形成如下结构.

  通过spring.profiles.active和spring.profiles.default可以设置激活哪个profile,如果是多个就用","分开, spring优先使用spring.profiles.active的设置, 如果找不到, 就使用spring.profiles.default设置的值, 如果二者都没设置, spring会认为没有要激活的profile, 它只会创建不加@Profile的那些bean.

  可以通过多种方式设置这两个属性:

  DispacherServlet的初始化参数

  Web应用的上下文参数

  JNDI

  环境变量

  JVM系统属性

  测试类上使用@ActiveProfiles注解

  下面是web.xml中在上下文以及在servlet中设置spring.profiles.default的代码

  spring.profiles.default

  dev

  spring.profiles.default

  dev

  条件化的 bean

  Spring4.0 引入了条件化bean, 这些bean只有在满足一定条件下才会创建. profile就是条件化的一种使用方式, 事实上4.0版本后的profile实现机制与其他条件化bean完全一样, 我们可以通过profile的源码一窥条件化bean的机制.

  @Target({ElementType.TYPE, ElementType.METHOD})

  @Retention(RetentionPolicy.RUNTIME)

  @Documented

  @Conditional(ProfileCondition.class) //注意这里, 条件化bean的注解

  public @interface Profile {

  String[] value();

  }

  @Conditional(ConditionClass.class) Java Config 可以将这个注解添加到@Bean注解的方法上, 参数value是一个Class类型的变量, 这个类必须实现Condition接口, 方法matchs()返回true则加载bean, 否则忽略.

  Condition 接口定义如下:

  @FunctionalInterface

  public interface Condition {

  boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

  }

  ProfileCondition的定义

  class ProfileCondition implements Condition {

  @Override

  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

  // 获取profile注解的属性

  MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());

  if (attrs != null) {

  // 获取value属性的值

  for (Object value : attrs.get("value")) {

  // 测试profile是否激活

  if (context.getEnvironment().acceptsProfiles((String[]) value)) {

  return true;

  }

  }

  return false;

  }

  return true;

  }

  }

  通过matchs()方法的两个参数我们可以做到 (1)通过ConditionContext获取到上下文所需信息;(2)通过AnnotatedTypeMetadata获取到bean的注解

  public interface ConditionContext {

  // 获取bean的注册信息, 从而可以检查bean定义

  BeanDefinitionRegistry getRegistry();

  // 获取bean工厂,从而可以判断bean是否存在,获取其他bean,获取bean的状态信息

  @Nullable

  ConfigurableListableBeanFactory getBeanFactory();

  // 获取环境变量,profile中正是使用此对象的方法acceptsProfiles()检查profile是否激活

  Environment getEnvironment();

  // 获取加载的资源

  ResourceLoader getResourceLoader();

  // 获取classLoader

  @Nullable

  ClassLoader getClassLoader();

  }

  public interface AnnotatedTypeMetadata {

  // 检查是否由某注解

  boolean isAnnotated(String annotationName);

  // 下面几个方法可以获取bean的注解,包括其属性

  @Nullable

  Map getAnnotationAttributes(String annotationName);

  @Nullable

  Map getAnnotationAttributes(String annotationName, boolean classValuesAsString);

  @Nullable

  MultiValueMap getAllAnnotationAttributes(String annotationName);

  @Nullable

  MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);

  }

  自动装配的歧义

  自动装配大大简化了spring的配置, 对于大多数应用对象的依赖bean, 程序实现的时候一般只有一个匹配, 但也存在匹配到多个bean的情况, Spring处理不了这种情况, 这时候就需要由开发人员为其消除装配的歧义.

  @Primary 注解用来指定被注解的bean为首选bean. 但如果多个匹配的bean都添加了该注解依然无法消除歧义.

  @Qualifier 注解用来限定bean的范围. 与@AutoWired和@Inject共同使用时,参数value用来指定beanid, 但使用默认的beanid对重构不友好. 与@Component或@Bean一起使用时,参数value为bean指定别名, 别名一般是带有描述bean特征的词描述, 然后在注入时就可以使用别名. 但别名可能需要定义多个, Qualifier不支持数组, 也不允许定义多个(原因见下面).

  @Qualifier 不允许对同一bean重复标记, 这是因为Qualifier注解定义没有添加@Repeatable注解.

  可以使用自定义限定注解的方式达到缩小bean范围的目的.

  下面代码使用Qualifier的方式定义bean

  @Component

  @Qualifier("cheap")

  public class QQCar implements Car{

  }

  @Component

  @Qualifier("fast")

  public class BenzCar implements Car{

  }

  @Component

  @Qualifier("safe")

  //@Qualifier("comfortable") //行不通,不能加两个Qualifier

  public class BMWCar implements Car{

  }

  再使用自定义限定注解的方式

  // 定义

  ...

  @Qualifier

  public @interface Cheap{}

  ...

  @Qualifier

  public @interface Fast{}

  ...

  @Qualifier

  public @interface Safe{}

  ...

  @Qualifier

  public @interface Comfortable{}

  // 使用

  @Component

  @Safe

  @Comfortable

  public class BMWCar implements Car{

  }

  ...

  使用自定义限定的方式可以随意组合来限定bean的范围, 因为没有任何使用string类型, 所以也是类型安全的.

  bean 作用域

  bean的四种作用域:

  单例(Singleton) 默认为此作用域, 全局唯一

  原型(Prototype) 注入或从上下文获取时均会创建

  会话(Session) Web程序使用, 同一会话保持唯一

  请求(Request) Web程序使用,每次请求唯一

  @Scope Java Config 用此注解指定bean的作用域, 参数value用来指定作用域, 如value=ConfigurableBeanFactory.SCOPE_PROTOTYPE, 原型和单例的常数定义在ConfigurableBeanFactory中, 会话和请求的常数定义在WebApplicationContext中 ; 另一个参数proxyMode=ScopedProxyMode.INTERFACES用来指定创建代理的方式, 示例中指定了使用基于接口的代理方法; 如果使用proxyMode=ScopedProxyMode.TARGET_CLASS则会使用CGLib来生成基于类的代理.

  作用域为什么使用代理模式? 如果bean不是单例的, spring会为其创建一个代理对象, 再将这个代理对象注入进去, 实际运行过程中由代理对象委托调用实际的bean. 这样有两点好处:(1)懒加载, bean可以在需要时才创建;(2)便于作用域扩展

  属性占位符和SpEL

  spring提供了两种运行时注入值的方式, 这样就避免了将字面量硬编码到程序或配置文件里面.

  属性占位符

  SpEL

  一. 属性占位符

  先看下面的例子:

  @Configuration

  @PropertySource("classpath:/path/app.property")

  public class MyConfig{

  @AutoWired

  Environment env;

  @Bean

  public Car getCar(){

  return new QQCar(env.getProperty("qqcar.price"));

  }

  }

  @PropertySource("classpath:/path/app.property") 可以指定加载资源文件的位置, 结合使用Environment类的实例env,可以获取到资源文件中配置的节点.Environment不仅可以获取到配置的字面量, 它也可以获取复杂类型, 将其转化为对应Class的实例. T getProperty(String key, Class targetType);XML Congif 可以使用占位符, 形如${qqcar.price}, 前提是配置了context命名空间下的它会生成类型为PropertySourcesPlaceholderConfigurer的bean, 该bean用来解析占位符

  如果开启组件扫描和自动装配的话, 就没有必要指定资源文件或类了, 可以使用@Value(qqcar.price). 同样, 需要一个类型为PropertySourcesPlaceholderConfigurer的bean.

  @Bean

  public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){

  return new PropertySourcesPlaceholderConfigurer();

  }

  二. 功能更强大的SpEL

  SpEL的特性如下:

  使用bean的Id来引用bean

  调用方法和属性: 可以通过beanid来调用bean的方法和属性, 就像在代码中一样.

  对值进行算术,逻辑,关系运算: 特别注意: ^乘方运算符; ?:为空运算符

  正则表达式匹配: 形如#{beanid.mobile matches '1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\\d|9\\d)\\d{8}'}

  集合操作: .?[]查找运算符; .^[]查找匹配的第一项;.$[]查找匹配的最后一项;.![]投影运算符(比如获取集合中符合条件的对象, 并取其中某个属性映射到结果集合中);

  SpEL的示例 #{1},#{T(System).out.print("test")},#{beanId.name},#{systemProperies["path"]}

  虽然SpEL功能强大, 我们可以通过SpEL编写复杂的表达式, 但过分复杂的表达式不适合理解和阅读, 同时也会增大测试的难度. 因此建议尽量编写简洁的表达式.

       小编:更多相关内容请点击课课家提供的链接,也可以直接关注课课家教育,获取更多专业的学习资料!

赞(0)
踩(0)
分享到:
华为认证网络工程师 HCIE直播课视频教程