【SpringBoot】@Configration与@Bean的使用

一.前言

Spring Boot 推荐使用 java 配置完全代替 XML 配置,java 配置是通过 @Configration 和 @Bean 注解实现的。

  • @Configration:作用在上,声明当前类是一个配置类,相当于 Spring 中的一个 XML配置文件,可理解为用Spring的xml配置文件里的<beans>标签。
  • @Bean:作用在方法上,声明当前方法的返回值是一个 Bean相当于Spring的XML配置文件中的<bean>标签

二.使用Java配置方式基础Shiro框架

这些xml配置和java代码大概瞄一下就行了,重点是要了解用xml配置文件Java配置方式区别

例如: 在spring项目中我们集成第三方的框架如shiro会在spring.xml配置文件中进行配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- 提供shiro的相关配置,简单的说,就是把shiro.ini里的内容搬到这个xml文件里面来了,只是写法不同-->

    <!--首先声明自定义URL匹配过滤器 --> <!--TODO:新增-->
    <bean id="urlPathMatchingFilter" class="com.oyjp.filter.URLPathMatchingFilter"/>

    <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 调用我们配置的权限管理器 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- 配置我们的登录请求地址 -->
        <property name="loginUrl" value="/login"/>
        <!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 -->
        <property name="unauthorizedUrl" value="/unauthorized"/>
        <!-- 退出 -->
        <property name="filters">
            <util:map>
                <entry key="logout" value-ref="logoutFilter"/>
                <entry key="url" value-ref="urlPathMatchingFilter" /> <!--TODO:新增-->
            </util:map>
        </property>
        <!-- 权限配置 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- anon表示此地址不需要任何权限即可访问 -->
                /login=anon
                /index=anon
                /static/**=anon
                <!-- 只对业务功能进行权限管理,权限配置本身不需要没有做权限要求,这样做是为了不让初学者混淆:大概的意思就是所有 /config的请求都不进行权限校验 -->
                /config/**=anon
                /doLogout=logout
                /404=anon
                /500=anon
                /favicon.ico=anon
                /unauthorized=anon
                <!--所有的请求(除去配置的静态资源请求或请求地址为anon的请求)都要通过登录验证,如果未登录则跳到/login -->
                <!--/** = authc-->
                <!--所有的请求(除去配置的静态资源请求或请求地址为anon的请求)都要通过过滤器url TODO:新增 -->
                /** = url
            </value>
        </property>
    </bean>

    <!-- 退出过滤器 -->
    <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <property name="redirectUrl" value="/index"/>
    </bean>

    <!-- 会话ID生成器 -->
    <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
    <!-- 会话Cookie模板 关闭浏览器立即失效 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="sid"/>
        <property name="httpOnly" value="true"/>
        <property name="maxAge" value="-1"/>
    </bean>
    <!-- 会话DAO -->
    <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
        <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
    </bean>
    <!-- 会话验证调度器,每30分钟执行一次验证 ,设定会话超时及保存 -->
    <bean name="sessionValidationScheduler"
          class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
        <property name="interval" value="1800000"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>
    <!-- 会话管理器 -->
    <bean id="sessionManager"  class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- 全局会话超时时间(单位毫秒),默认30分钟 -->
        <property name="globalSessionTimeout" value="1800000"/>
        <property name="deleteInvalidSessions" value="true"/>
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
        <property name="sessionDAO" ref="sessionDAO"/>
        <property name="sessionIdCookieEnabled" value="true"/>
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
        <property name="sessionIdUrlRewritingEnabled" value="false" /> <!--去掉URL中的JSESSIONID-->
    </bean>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="databaseRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>

    <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
        <property name="arguments" ref="securityManager"/>
    </bean>

    <!-- 密码匹配器 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5"/> <!--加密算法-->
        <property name="hashIterations" value="2"/> <!--加密次数-->
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>

    <!-- DatabaseRealm 真正做登录验证和授权的地方-->
    <bean id="databaseRealm" class="com.oyjp.realm.DatabaseRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>

    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
</beans>

使用@Configration+@Bean可以将原本在xml中的配置,转移到Java代码中进行配置,被
@Configration修饰的类称为"配置类"

  • 使用 @Configration 注解将该类ShiroConfiguration 声明为一个配置类。
  • 在方法上添加@Bean注解则会往Spring 容器默认添加一个名为方法名首字母小写的Bean,该 Bean 即为方法的返回值。
@Configuration
public class ShiroConfiguration {
    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     * <p>
     * Filter Chain定义说明
     * 1、一个URL可以配置多个Filter,使用逗号分隔
     * 2、当设置多个过滤器时,全部验证通过,才视为通过
     * 3、部分过滤器可指定参数,如perms,roles
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        //拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //自定义拦截器
        Map<String, Filter> customisedFilter = new HashMap<>();
        customisedFilter.put("url", getUrlPathMatchingFilter());
        //配置映射关系
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/index", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/config/**", "anon");
        filterChainDefinitionMap.put("/doLogout", "logout");
        filterChainDefinitionMap.put("/**", "url");
        shiroFilterFactoryBean.setFilters(customisedFilter);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }
    /**
     * 用于实例化自定义URL匹配过滤器
     * URLPathMatchingFilter 并没有用@Bean管理起来。 原因是Shiro的bug, 这个也是过滤器,ShiroFilterFactoryBean 也是过滤器,当他们都出现的时候,默认的什么anno,authc,logout过滤器就失效了。所以不能把他声明为@Bean。
     */
    public UrlPathMatchingFilter getUrlPathMatchingFilter() {
        return new UrlPathMatchingFilter();
    }
    /**
     * 实例化安全管理器并设置DatabaseRealm(真正做登录验证和授权的地方)
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置realm.
        securityManager.setRealm(getDatabaseRealm());
        return securityManager;
    }
    @Bean
    public DatabaseRealm getDatabaseRealm() {
        DatabaseRealm myShiroRealm = new DatabaseRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }
    /**
     * 实例化凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了所以我们需要修改下doGetAuthenticationInfo中的代码; )
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列的次数,比如散列两次,相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }
    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
     * 实例化LifecycleBeanPostProcessor  保证实现了Shiro内部lifecycle函数的bean执行
     */
    @Bean
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
} 

三.@Configuration

3.1.@Configuration作用

  • @Configuration底层是含有@Component,所以@Configuration 具有和 @Component的作用。
  • @Configuration 用于定义配置类,可理解为Spring的xml配置文件里面的<beans>标签。
  • @Configration 标注的类不能是final 类型
  • @Configration 标注类中可以声明一个或多个 @Bean方法
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}

@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans>

作用为配置spring容器(应用上下文)

@Configuration
public class TestConfiguration {
    public TestConfiguration() {
        System.out.println("TestConfiguration容器启动初始化。。。");
    }
}

相当于

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
    xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false">

</beans>

3.2.@Configuration使用

@Configration标注的类中可以声明多个@Bean方法,并且 Bean 与 Bean 之间是可以有依赖关系的
如果一个 bean 的依赖其他 bean,可以注入方式有:

  1. 直接调用对配置类中依赖Bean的方法
  2. 形参上使用@Qualifier("beanName")通过Bean的名字注入实例
  3. 形参上使用@Autowired通过Bean的类型注入实例

相关Bean

@Data
public class TestBean {
    private String username;
    private String url;
    private String password;
}
@Data
public class TestBean2{
	//省略类结构,和TestBean一样
}
@Data
public class TestBean3{
	//省略类结构,和TestBean一样
}
@Data
public class TestBean4{
	//省略类结构,和TestBean一样
}
@Configuration
public class TestConfiguration {
@Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setUsername("admin");
        testBean.setPassword("123456");
        testBean.setUrl("www.testBean.com");
        return testBean;
    }
    /**
     * 使用@Qualifier通过Bean的名字注入实例
     */
    @Bean
    public TestBean2 testBean2(@Qualifier("testBean") TestBean testBean) {
        TestBean2 testBean2 = new TestBean2();
        testBean2.setUsername(testBean.getUsername());
        testBean2.setPassword(testBean.getPassword());
        testBean2.setUrl("www.TestBean2.com");
        return testBean2;
    }
    /**
     * 使用@Autowired通过Bean的类型注入实例
     */
    @Bean
    public TestBean3 testBean3(@Autowired TestBean2 testBean2) {
        TestBean3 testBean3 = new TestBean3();
        testBean3.setUsername(testBean2.getUsername());
        testBean3.setPassword(testBean2.getPassword());
        testBean3.setUrl("www.TestBean3.com");
        return testBean3;
    }
    /**
     * 调用Bean的方法获取依赖Bean
     */
    @Bean
    public TestBean4 testBean4() {
        TestBean testBean = testBean();//获取TestBean实例

        TestBean4 testBean4 = new TestBean4();
        testBean4.setUsername(testBean.getUsername());
        testBean4.setPassword(testBean.getPassword());
        testBean4.setUrl("www.testBean4.com");
        return testBean4;
    }
}

3.3.@ConditionalOnProperty

使用条件注解@ConditionalOnProperty,可以控制是否将当前的配置类@Configuration注册到Spring容器之中

  • 它作用于接口、类、枚举、注解、方法之上
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
    String[] value() default {}; //数组,获取对应property名称的值,与name不可同时使用  
  
    String prefix() default "";//property名称的前缀,可有可无  
  
    String[] name() default {};//数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用  
  
    String havingValue() default "";//可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置  
  
    boolean matchIfMissing() default false;//缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错  
  
    boolean relaxedNames() default true;//是否可以松散匹配,至今不知道怎么使用的   
}

使用方法
通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值。

  • 如果该值为空,则返回false;
  • 如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false
  • 如果返回值为false,则该@configuration不生效;为true则生效。
@Configuration
//在application.properties配置"mf.assert",对应的值为true
@ConditionalOnProperty(prefix="mf",name = "assert", havingValue = "true")
public class AssertConfig {
    @Autowired
    private HelloServiceProperties helloServiceProperties;
    @Bean
    public HelloService helloService(){
        HelloService helloService = new HelloService();
        helloService.setMsg(helloServiceProperties.getMsg());
        return helloService;
    }
}

3.4.@ConfigurationProperties

@ConfigurationProperties 和 @value 有着相同的功能,但是写法更为方便,通过指定前缀,绑定配置文件中的配置,该注解可以放在上,也可以放在方法

  • @ConfigurationProperties 的 POJO类的命名比较严格,因为它必须和prefix的后缀名要一致, 不然值会绑定不上, 特殊的后缀名是“driver-class-name”这种带横杠的情况,在POJO里面的命名规则是 下划线转驼峰 就可以绑定成功,所以就是 “driverClassName”
  • 如果一个类只使用了@ConfigurationProperties注解,然后该类没有在扫描路径下或者没有使用@Component等注解,导致无法被扫描为bean,那么就必须在配置类上使用@EnableConfigurationProperties(xxx.class)注解去指定这个类,这个时候就会让该类上的@ConfigurationProperties生效,然后作为bean添加进spring容器中
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
    @AliasFor("prefix")//配置属性的前缀
    String value() default "";

    @AliasFor("value")
    String prefix() default "";

    boolean ignoreInvalidFields() default false;

    boolean ignoreUnknownFields() default true;
}

使用方法
配置文件

spring.datasource.url=jdbc:mysql://127.0.0.1:8888/test?useUnicode=false&autoReconnect=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

配置映射类

@Data
@ConfigurationProperties(prefix = "spring.datasource")
@Component
public class DatasourcePro {
    private String url;
    private String username;
    private String password;
    // 配置文件中是driver-class-name, 转驼峰命名便可以绑定成
    private String driverClassName;
}

@Autowired注入

@Controller
@RequestMapping(value = "/config")
public class ConfigurationPropertiesController {
    @Autowired
    private DatasourcePro datasourcePro;
}

四.@Bean

4.1.@Bean作用

@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的<bean>

作用为注册bean对象

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    /** @deprecated */
    @Deprecated
    Autowire autowire() default Autowire.NO;

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}
  • name: 指定bean的名字,并且可以接受一个数组,配置多个name,默认采用的是 "方法名" + "首字母小写"的命名方式
  • initMethod: 初始化Bean时执行的方法名
  • destoryMethod: 销毁Bean时执行的方法名

或者使用通过@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作 。

配置类

@Configuration
public class TestConfiguration {
    public TestConfiguration() {
        System.out.println("TestConfiguration容器启动初始化。。。");
    }

    // @Bean注解注册bean,同时可以指定初始化和销毁方法
    @Bean(initMethod = "init",destroyMethod = "destroy")
    @Scope("prototype")//每次从Spring容器获取都会新建一个TestBean 对象
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setUsername("admin");
        testBean.setPassword("123456");
        testBean.setUrl("www.baidu.com");
        return testBean;
    }
}

要被注册到Spring容器的Bean

@Data
public class TestBean {
    private String username;
    private String url;
    private String password;
    //Bean初始化方法
    public void init() {
        System.out.println("TestBean 初始化。。。");
    }
    //Bean销毁方法
    public void destroy() {
        System.out.println("TestBean 销毁。。。");
    }
}

上述操作相当于实例化TestBean ,并交给Spring容器管理

注:

  1. @Bean标注的方法,如果没有指定bean的名称,默认采用的是 “方法名” + "首字母小写"的命名方式
  2. @Bean注解默认作用域为单例singleton作用域,可通过@Scope("prototype")设置为原型作用域;
  3. @Bean的作用是注册bean对象,我们也可以使用@Component、@Controller、@Service、@Repository等注解注册bean(在需要注册的类上加注解),然后配置@ComponentScan注解进行自动扫描。

4.2.指定@Bean别名

Bean 名称
默认情况下 Bean 名称就是方法名,比如下面 Bean 名称便是 myBean

@Bean
public MyBean myBean() {
    return new MyBean();
}

@Bean 注解支持设置别名。

@Bean("myBean")
public MyBean myBean() {
    return new MyBean();
}

@Bean 注解支持设置多个别名。

@Bean({"myBean1","myBean2"})
public MyBean myBean() {
    return new MyBean();
}

4.3.@Bean 与其他注解一起使用

@Bean 注解常与 @Scope、@Lazy,@DependsOn 和 @Primary注解一起使用:

  • @Profile :可以在不同环境加载不同的Bean。如: 开发环境和生产环境加载不同的数据源Bean
  • @Scope :将 Bean 的作用域从单例改变为指定的作用域

    @Scope("prototype ") :每次获取 Bean 的时候会有一个新的实例

  • @Lazy :只有在默认单例作用域的情况下才有实际效果
  • @DependsOn :在当前 Bean 创建之前需要先创建其他 Bean,可以控制Bean的加载顺序,
  • @Primary: 当一种类型的Bean,可能会有几种不同的实现类,可以使用@Primary,让Sping容器默认注入某一个实例

4.3.@Bean初始化和销毁的回调

通过 @Bean 注解的initMethoddestrodMethod数学可以Bean 在初始化和销毁时会调用哪个方法

public class MyBean {
    public void init() {
        System.out.println("MyBean开始初始化...");
    }
    public void destroy() {
        System.out.println("MyBean销毁...");
    }
}
@Bean(initMethod="init", destroyMethod="destroy")
public MyBean myBean() {
    return new MyBean();
}

4.4.Bean的条件装配注解

  • @Conditional : 指定的Condition实现类matches方法返回true,则实例化一个Bean

    @Configuration
    public class BeanConfig {
    	//如果WindowsCondition的实现方法返回true,则注入这个bean    
    	@Conditional({WindowsCondition.class})
    	@Bean(name = "bill")
    	public Person person1(){
        	return new Person("Bill Gates",62);
    	}
    
    	//如果LinuxCondition的实现方法返回true,则注入这个bean
    	@Conditional({LinuxCondition.class})
    	@Bean("linus")
    	public Person person2(){
        	return new Person("Linus",48);
    	}
    }
    

    创建 LinuxCondition和 WindowsCondition类,并实现Condition接口

    public class LinuxCondition implements Condition {
    
    	@Override
    	public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        	Environment environment = conditionContext.getEnvironment();
        	String property = environment.getProperty("os.name");
        	if (property.contains("Linux")){
            	return true;
        	}
        	return false;
    	}
    }
    
  • @ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)

    //RedisOperBean依赖redisTemplate
    @Component
    @ConditionalOnBean(name="redisTemplate")
    public class RedisOperBean {
    	private final RedisTemplate redisTemplate;
    	public RedisOperBean(RedisTemplate redisTemplate) {}
    }
    
  • @ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)

    @Bean
    @ConditionalOnMissingBean(name = "imageValidateCodeGenerator")
    public ValidateCodeGenerator imageValidateCodeGenerator() {
    	ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
    	codeGenerator.setSecurityProperty(securityProperties);
    	return codeGenerator;
    }
    
  • @ConditionalOnClass(某个class位于classpath 上,才会实例化一个Bean)

    @Configuration
    @ConditionalOnClass(name = "this.clazz.does.not.Exist")
    class OnClassModule {}
    
  • @ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)

  • @ConditionalOnWebApplication: 是web应用

  • @ConditionalOnNotWebApplication(不是web应用)

  • @ConditionalOnResource:classpath下存在指定的资源文件,才会实例化一个Bean

    @Bean
    @ConditionalOnResource(resources="classpath:shiro.ini")
    protected Realm iniClasspathRealm(){}
    
  • @ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)

    @Configuration
    @ConditionalOnExpression("${enabled:false}")
    public class BigpipeConfiguration {
    	@Bean
    	public OrderMessageMonitor orderMessageMonitor(ConfigContext configContext) {
        	return new OrderMessageMonitor(configContext);
    	}
    }
    

    表达式其他用法

    @ConditionalOnExpression("${mq.cumsumer.enabled}==1&&${rabbitmq.comsumer.enabled:true}")
    @ConditionalOnExpression("'${mq.comsumer}'.equals('rabbitmq')")
    

SpringBoot为我们提供的配置类有180多个,但是我们不可能会全部引入。所以在自动装配的时候,会去ClassPath下面寻找,是否有对应的配置类。如果有配置类,则按条件注解 @Conditional或者@ConditionalOnProperty等相关注解进行判断,决定是否需要装配。如果classPath下面没有对应的字节码,则不进行任何处理。

SpringBoot的所有条件装配注解

五.@Order

@Order源码

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
	/**
	 * 默认是最低优先级,值越小优先级越高
	 */
	int value() default Ordered.LOWEST_PRECEDENCE;
}

  • 可以标注在类(接口、枚举)、方法、属性(包括枚举常量);
  • 定义了Bean的加载顺序,可为负数,order的值越小,优先级越高
  • order如果不标注数字,默认最低优先级,因为其默认值是int最大值

例如:
@Order(-1)优先于@Order(0)
@Order(1)优先于@Order(2)

@Configuration
@Order(2)
public class AConfig {
  @Bean
  public AService AService() {
    System.out.println("AService 加载了");
    return new AService();
  }
 
}
@Configuration
@Order(1)
public class BConfig {
  @Bean
  public BService bService() {
    System.out.println("BService 加载了");
    return new BService();
  }
}

order的值越小,优先级越高
order如果不标注数字,默认最低优先级,因为其默认值是int最大值
该注解等同于实现Ordered接口getOrder方法,并返回数字。

六.@Import

6.1.@Import

6.1.1.简介

将一个对象注册到Spring容器,有4种做法:

  1. @Bean
  2. @Componet(@Service等归为一类)
  3. @Import
  4. Spring的xml配置文件Bean标签

@Import

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

  • 参数value接收一个Class数组,将传入的Class对象以全类名作为Bean名称加入Spring容器中

    全类名是某个文件在项目中的位置,格式为包名.类名

  • value的值可以为Class、ImportSelector、ImportBeanDefinitionRegistrar
6.1.2.使用1-注册Bean
  1. 配置类使用@Import导入要加载到Spring容器的类Class
public class Banana {}
public class Apple {}
  1. 在有@Configuration标注的配置类上使用@Import导入
@Configuration
@Import({Apple.class, Banana.class})
public class AppConfig {}
  1. 测试类ImportTest 中将配置类AppConfig注册到Spring容器中,并打印容器中Bean的名称
public class ImportTest {
    public static void main(String[] args) {
        /**
         * 在传统 XML 方法中,您可使用 ClassPathXmlApplicationContext 类来加载外部 XML 上下文文件。
         * 但在使用基于 Java 的配置时,有一个 AnnotationConfigApplicationContext 类。
         * AnnotationConfigApplicationContext 类是 ApplicationContext 接口的一个实现,使您能够注册@Configuration所标注的配置类。
         */
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        //获取Bean定义所有名称
        for (String str : applicationContext.getBeanDefinitionNames()) {
            System.out.println(str);
        }
    }
}

在这里插入图片描述

6.1.3.使用2-合并配置类

类似于Spring配置文件的 <import>标签一样,可以合并多个配置文件

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xsi:schemaLocation="  
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
       ">  
       
    <import resource="xxxx-service.xml"/>  
    <import resource="xxx-dao.xml"/>  
</beans>

在代码中也可以使用@Import合并多个@Configuration标注的配置类

  1. 还是使用上面定义的Apple和Banana类,新增两个配置类AppleConfig,BananaConfig,并注册对应的Bean到Spring容器中
@Configuration
public class AppleConfig {
    @Bean
    public Apple apple() {
        return new Apple();
    }
}
@Configuration
public class BananaConfig {
    @Bean
    public Banana banana() {
        return new Banana();
    }
}
  1. 修改配置类,在@Import中加入AppleConfig ,BananaConfig
@Configuration
@Import({AppleConfig.class, BananaConfig.class})
public class AppConfig {}
  1. 测试代码ImportTest 不变,打印结果:配置类以及相应Bean都注册到了Spring容器
    在这里插入图片描述

6.2.ImportSelector

ImportSelector强调的是复用性创建一个实现ImportSelector接口的类,重写selectImports方法,该方法返回值是字符串数组,也就是需要注入Spring容器中的Bean的全类名。注册到Spring容器中的Bean名称同样也是全类名。

  1. 定义一个类
public class Berry {}
  1. 自定义selector实现ImportSelector接口,在方法中返回自定义的类路径,Spring会自动将该路径下的类注入到容器中
public class BerryImportSelector implements ImportSelector {
    /**
     * @param annotationMetadata 当前标注@Import注解的类的所有注解信息
     * @return 返回值就是导入到容器中的组件全类名
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.ws.im.Berry"};
    }
}
  1. 修改配置类,在@Import中加入BerryImportSelector :
@Configuration
@Import({Apple.class, Banana.class, BerryImportSelector.class})
public class AppConfig {}
  1. 测试代码ImportTest 不变,打印结果:Berry确实被注入进Spring容器
    在这里插入图片描述

6.3.ImportBeanDefinitionRegistrar

  • Spring官方就是用这种方式,实现了@Component、@Service等注解动态注入机制
  • 定义一个ImportBeanDefinitionRegistrar接口的实现类,实现registerBeanDefinitions方法,然后在有@Configuration标注的配置类上使用@Import导入
  1. 定义一个类
public class Tomato {}
  1. 创建TomatoRegistrar实现ImportBeanDefinitionRegistrar接口,在方法当中将类注册到容器里,并将beanName修改为MyTomato:
public class TomatoRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * @param annotationMetadata     当前标注@Import注解的类的所有注解信息
     * @param beanDefinitionRegistry BeanDefinition的注册类
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Tomato.class);
        beanDefinitionRegistry.registerBeanDefinition("MyTomato", beanDefinition);
    }
}
  1. 修改配置类,将TomatoRegistrar放入@Import中
@Configuration
@Import({Apple.class, Banana.class, BerryImportSelector.class, TomatoRegistrar.class})
public class AppConfig {}
  1. 测试代码ImportTest 不变,打印结果:Tomato确实被注入进Spring容器
    在这里插入图片描述

6.5.使用@Import控制类注入时机

通过一个简单的开关来控制是否注入Berry类,我们可以定义一个注解

@Retention(RetentionPolicy.RUNTIME)
@Import({BerryImportSelector.class})
public @interface EnableBerry {}

在AppConfig类加上@EnableBerry注解,该注解标注在配置类上面就会注册一个BerryImportSelector的Bean到Spring容器中

@Configuration
@Import({Apple.class, Banana.class, TomatoRegistrar.class})
@EnableBerry
public class AppConfig {
}

SpringCloud中的@EnableEureka、@EnableDiscoveryClient就是利用这个原理


Spring Boot不是Spring的加强版,所以@Configuration和@Bean同样可以用在普通的spring项目中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墩墩分墩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值