一.前言
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,可以注入方式有:
- 直接调用对配置类中
依赖Bean的方法
- 在
形参
上使用@Qualifier("beanName")
通过Bean的名字
注入实例 - 在
形参
上使用@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容器管理
注:
- @Bean标注的
方法
,如果没有指定bean的名称
,默认采用的是 “方法名” + "首字母小写"的命名方式 - @Bean注解
默认作用域为单例singleton作用域
,可通过@Scope("prototype")
设置为原型作用域; - @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 注解的initMethod
和 destrodMethod
数学可以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下面没有对应的字节码,则不进行任何处理。
五.@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种做法:
- @Bean
- @Componet(@Service等归为一类)
- @Import
- 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
- 配置类使用@Import导入要加载到Spring容器的类Class
public class Banana {}
public class Apple {}
- 在有
@Configuration
标注的配置类
上使用@Import导入
@Configuration
@Import({Apple.class, Banana.class})
public class AppConfig {}
- 测试类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标注的配置类
- 还是使用上面定义的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();
}
}
- 修改配置类,在@Import中加入AppleConfig ,BananaConfig
@Configuration
@Import({AppleConfig.class, BananaConfig.class})
public class AppConfig {}
- 测试代码ImportTest 不变,打印结果:配置类以及相应Bean都注册到了Spring容器
6.2.ImportSelector
ImportSelector强调的是复用性
,创建一个实现ImportSelector接口的类
,重写selectImports方法
,该方法返回值是字符串数组
,也就是需要注入Spring容器中的Bean的全类名
。注册到Spring容器中的Bean名称同样也是全类名。
- 定义一个类
public class Berry {}
- 自定义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"};
}
}
- 修改配置类,在@Import中加入BerryImportSelector :
@Configuration
@Import({Apple.class, Banana.class, BerryImportSelector.class})
public class AppConfig {}
- 测试代码ImportTest 不变,打印结果:Berry确实被注入进Spring容器
6.3.ImportBeanDefinitionRegistrar
- Spring官方就是用这种方式,实现了
@Component、@Service等注解
的动态注入机制
。 - 定义一个
ImportBeanDefinitionRegistrar接口
的实现类,实现registerBeanDefinitions方法
,然后在有@Configuration
标注的配置类
上使用@Import导入
- 定义一个类
public class Tomato {}
- 创建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);
}
}
- 修改配置类,将TomatoRegistrar放入@Import中
@Configuration
@Import({Apple.class, Banana.class, BerryImportSelector.class, TomatoRegistrar.class})
public class AppConfig {}
- 测试代码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项目
中。