新闻资讯

新闻资讯 行业动态

Spring基础——控制反转(IOC)(二)

编辑:006     时间:2020-02-14
3.1.2 激活Profile

Spring 在确定哪个 Profile 处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。前者会根据指定值来确定哪个 Profile 是激活的;后者是当没有指定active属性的值时,默认激活的 Profile。Spring 中设置这两个属性的方式

  • 作为 DispatcherServlet 的初始化参数;
  • 作为 Web 应用的上下文参数;
  • 作为 JNDI 条目;
  • 作为环境变量;
  • 作为 JVM 的系统属性;
  • 在记成测试类上,使用@ActiveProfiles注解设置;

下面的例子中,使用 DispatcherServlet 的参数将spring.profiles.default设置 profile。在 Web 应用中,设置spring.profiles.default的 web.xml 文件如下

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--为上下文设置默认的 profile--> <context-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </context-param> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>cn.book.main.servlet.DispatcherServlet</servlet-class> <!--为Servlet设置默认的 profile--> <init-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/appServlet</url-pattern> </servlet-mapping> </web-app>

注意

spring.profiles.active和spring.profiles.default属性中,profile 使用的是复数形式,可以同时激活多个 Profile——通过列出多个 profile 名称,并以逗号分隔。

3.2 条件化 Bean

如果我们定义的 bean ,但不希望它们被 Spring 容器即刻被创建,而是希望当类路径下包含某个库,或者是创建了其它 Bean,亦或者要求设置了某个特定环境变量后,该 Bean 才被创建。此时,我们就需要使用条件化配置

要实现一个条件化 Bean,在装配 Bean 的方法上( 使用@Bean),引用另一个注解@Conditional(*.class),注意:括号内给定的是一个类文件。该注解会根据括号内给定类的返回结果判断是否创建 Bean,如果为true,会创建 Bean,否则不创建

但是,这只是定义了一个要条件化的 Bean,该 Bean 需要满足怎样的条件,需要自己实现。上面说到,@Conditional注解需要传入一个类文件,该类在创建时,要实现Condition接口,并重写matches()方法。下面是一个简单的例子

package cn.book.main.pojo; //Bean 类 public class TestCondition { public TestCondition() {
        System.out.println("Bean 被创建了");
    }
}

该类实现Condition接口,并重写matches()方法,在方法内可以编写判断代码,并返回 boolean 值。

package cn.book.main.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; public class IfCreatCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { return false;
    }
}

配置类,装配 Bean。

package cn.book.resource; import cn.book.main.condition.IfCreatCondition; import cn.book.main.pojo.TestCondition; import org.springframework.context.annotation.*; @Configuration public class HumanJobConfig { @Bean @Conditional(IfCreatCondition.class) public TestCondition getCondition(){ return new TestCondition();
    }
}

测试类,如果 IfCreatCondition 类返回 true,则 Bean 被创建;否则不会被创建。

package cn.book.test; import cn.book.resource.HumanJobConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=cn.book.resource.HumanJobConfig.class) public class HumanJobTest { @Test public void Test(){

        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(HumanJobConfig.class);
    }
}

上面只是演示了实现条件化 Bean 的流程,我们的条件可以再复杂。大家应该注意到了,matches()中有两个参数:ConditionContext和AnnotatedTypeMetadata。通过这两个对象,我们可以实现符合 IOC 和 DI 的条件。接下来,就来了解这两个对象:

ConditionContext是一个接口,它有以下方法

方法 描述
getRegistry 返回 BeanDefinitionRegistry 检查 bean 定义;
getBeanFactory 返回 ConfigurableListableBeanFactory 检查 bean 是否存在,甚至 检查 bean 的属性;
getEnvironment 返回 Environment 检查环境变量是否存在以及它的值是什么;
getResourceLoader 返回 ResourceLoader 所加载的资源;
getClassLoader 返回 ClassLoader 加载并检查类是否存在;

AnnotatedTypeMetadata也是一个接口,能够检查带有@Bean注解的方法上还有什么注解。它有以下方法:

方法 描述
boolean isAnnotated(String annotationType) 检查带@Bean的方法上是否存在其它特定的注解
Map<String,Object> getAnnotationAttributes(String annotationType) 获得指定注解的 Bean
Map<String,Object> getAnnotationAttributes(String annotationType, boolean classValueAsString) ==未了解==
MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType) 获得指定注解的所有 Bean
MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType, boolean classValueAsString) ==未了解==

3.3 处理自动装配的歧义性

自动化装配中,仅当只有一个 Bean 满足时,才能装配成功。当多个 bean 满足装配时,Spring 会产生异常:NoUniqueBeanDefinitionException。最常见的情况是:==当一个接口有多个实现类,调用时使用接口对象引用子类==。

比如:Human接口有两个实现类:Man类和 Woman类

package cn.book.main.entity; public interface Human {
}
package cn.book.main.entity; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component public class Man implements Human { public Man() {
        System.out.println("I am man");
    }
}
package cn.book.main.entity; import org.springframework.stereotype.Component; @Component public class Woman implements Human { public Woman() {
        System.out.println("I am woman");
    }
}

配置类

package cn.book.resource; import cn.book.main.pojo.TestCondition; import org.springframework.context.annotation.*; @Configuration @ComponentScan("cn.book.main.entity") public class HumanConfig {

}

测试类,自动注入一个Human接口。此时,spring会产生:NoUniqueBeanDefinitionException。

package cn.book.test; import cn.book.main.entity.Human; import cn.book.resource.HumanJobConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=cn.book.resource.HumanJobConfig.class) public class HumanJobTest { @Autowired private Human human; @Test public void Test(){
        System.out.println(human.getClass());
    }
}

当确实发生装配的歧义性时,Spring 提供了以下方案:

  1. 将可选 Bean 中的某一个设为首选(primary) 的 Bean;
  2. 使用限定符(qualifier)限定到符合的、唯一的 Bean;
3.3.1 标示首选 Bean

标示首选需要使用关键字 primary,它在 JavaConfig 中是注解@Primary,在 XML 是bean元素中的属性 primary。

JavaConfig 配置

@Primary注解配合@Component和@Bean注解组合使用,在需要设置为首选的组件类Bean 对象上。

与@Component注解配合使用

@Component @Primary public class Man{

}

或者,与@Bean注解配合使用

@Configuration public class JavaConfig{ @Bean @Primary public Human getMan(){ return new Man();
    }
}

在 XML 中设置 Bean 为首选项的配置为:

<bean id="man" class="Man" primary="true"/>

缺点

  • 不能设置多个首选 Bean;
  • 不够灵活,存在歧义性时,只能装配使用设置首选的Bean;
3.3.2 限定符限定装配

限定符@qualifier注解,主要作用是在可选的 Bean 进行缩小范围选择,直到找到满足的 Bean。它的有两个作用:

  1. 与@Autowired和@Inject协同使用,在注入的时候指定想要注入的是哪个 Bean;

    @qualifier("")括号内所设置的参数时要注入 Bean 的 ID 值。

  2. 与@Component和@Bean协同使用,为 Bean 设定限定符;

    @qualifier("")括号内是为 Bean 设置的限定符,在注入时使用qualifier中引用。

3.3.3 限定符注解

如果使用注解@qualifier限定符依旧无法解决 bean 的装配歧义性问题时,而且,在 Spring 中无法重复使用相同的@qualiifer注解,在这种情况下,可以自定义注解来区分 bean。那么,如何自定义注解呢?

import org.springframework.beans.factory.annotation.Qualifier; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.CONSTRUCTOR,ElementType.FIELD,
        ElementType.METHOD,ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface 注解名 {
}

注意

  1. 自定义注解不能使用在类上;
  2. 使用自定义注解时,需要同时放在 声明Bean的地方 和 注入 Bean 的地方;

4、Bean 作用域

在默认情况下,Spring 应用上下文中所有的 Bean 都是以单例形式创建的。也就是,不管一个 Bean 被注入多少次,每次注入的 Bean 都是同一个实例。

如果一个实例需要保持无状态并在应用中重复使用,单例作用域是不可行且不安全的。在 Spring 定义了多种作用域,Spring 会基于这些作用域创建 Bean,这些作用域包括:

  • 单例(Singleton):在整个应用,只会创建 Bean 的一个实例;
  • 原型(Prototype):每次注入或通过 Spring 应用上下文获取时,都会创建一个新的 Bean 实例;
  • 会话(Session):在 Web 应用中,为每个会话创建一个 Bean 实例;
  • 请求(Request):在 Web 应用中,为每个请求创建一个 Bean 实例;

单例是默认的作用。如果想要选择其它作用域,要使用@Scope注解。注解内使用以下表示作用域的参数:

  1. ConfigurableBeanFactory.SCOPE_PROTUTYPE或者"prototype";
  2. ConfigurableBeanFactory.SCOPE_SESSION或者"session";
  3. ConfigurableBeanFactory.SCOPE_REQUEST或者"request";

如果使用 XML 配置,在<bean>元素中的属性scope设置 bean 的作用域。

4.1 会话和请求作用域

==学习到 Web 部分内容再深入学习==

5、运行时值注入

前面在装配 Bean,讲到在创建 Bean 时,将常量(比如int类型、String类型)直接给定,这是将值硬编码到 Bean 中。有时,为了避免硬编码值,想让这些值在运行时在确定,Spring 提供了两种在运行时求值的方式:

  • 属性占位符
  • Spring 表达式语言

5.1 注入外部值

回顾一下,在我们使用 JDBC 时,会创建一个属性文件*.properties文件放置连接数据库所需的配置参数。假设,在 Spring 中该文件依旧存在,我们如何在配置类或配置文件中解析并取值?

JavaConfig 配置类

  1. 通过注解@PropertySource中的value属性设置属性文件路径;
  2. 自动注入 Environment 对象;
  3. 通过 Environment 对象获取属性值;
@Configuration @PropertySource(value = "classpath:/JDBC.properties") public class JdbcConfig { @Autowired Environment env; @Bean public JdbcParams getJdbc(){ return new JdbcParams(
                env.getProperty("jdbc.driver"),
                env.getProperty("jdbc.url"),
                env.getProperty("jdbc.username"),
                env.getProperty("jdbc.password")
        );
    }
}
public class JdbcParams { private String driver; private String url; private String username; private String password; public JdbcParams() {
    } public JdbcParams(String driver, String url, String username, String password) { this.driver = driver; this.url = url; this.username = username; this.password = password;
    }
}

Environmen 接口的用法,通过 Environment 接口可以调用以下方法:

方法 描述
String getProperty(String key) 根据指定值获取属性,属性没有定义返回null
String getProperty(String key, String defaultValue) 根据指定值获取属性,如果没有属性值,则返回defaultValue;
T getProperty(String key, Class<T> type) 返回指定类型的属性值;type为指定类型的.class
T getProperty(String key, Class<T> type,T defaultValue) 返回指定类型的属性值;type为指定类型的.class,如果没有属性值,则返回defaultValue;
getRequiredProperty(String key) 根据指定值获取属性,属性没有定义抛出异常
containProperty(String key) 检查属性文件是否存在某个属性;
T getPropertyAsClass(String key,Class<T> type) 将属性文件解析为指定的类文件;
String[] getActiveProfiles() 返回激活 profile 名称的数组;
String[] getDefaultProfiles() 返回默认 profile 名称的数组;
boolean acceptsProfiles(String... profiles) 如果 environment 支持给定的 profile 的话,就返回 true;

5.2 占位符注入值

Spring 支持将属性定义到外部的属性文件中,并使用占位符将值插入到 Bean 中。在 Spring 装配中,占位符的形式为使用${...}包装的属性名称。

为了使用占位符,需要配置一个PropertySourcePlaceholderConfigurerBean,它能够基于 Environment 及其属性源来解析占位符。下面来看看,JavaConfig 配置和 XMl 配置中使用占位符的用法

import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; @Configuration //声明属性源,并将属性文件加载到Spring @PropertySource(value = "classpath:/JDBC.properties") public class StudentCongif {

     二、 //(1)使用占位符解析属性 @Bean public JdbcParams getJdbc(
            @Value("${jdbc.driver}") String driver,
            @Value("${jdbc.url}") String url,
            @Value("${jdbc.username}") String username,
            @Value("${jdbc.password}") String password){ return new JdbcParams(driver,url,username,password);
    } //(2)还需要配置一个PropertySourcesPlaceholderConfigurer 的 bean @Bean public PropertySourcesPlaceholderConfigurer placeholderConfigurer(){ return new PropertySourcesPlaceholderConfigurer();
    }
}
<!--创建 PropertySourceHolderConfigurer --> <context:property-placeholder location="classpath:/JDBC.properties"/> <!--    使用占位符进行值注入--> <bean id="jdbc" class="cn.book.main.valueInject.JdbcParams" c:driver="${jdbc.driver}" c:url="${jdbc.url}" c:username="${jdbc.username" c:password="${jdbc.password}"/>

解析外部属性能够将值的处理推迟到运行时,但它的关注点在于根据名称解析来自 Spring Environment 和属性源的属性

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

回复列表

相关推荐