1. 核心注解
在一个标准的java应用程序的入口处(main),我们需要核心注解。@SpringBootApplication
它是一个组合注解,包含以下注解
1 2 3 4 5 6 @springBootApplication public class DemoApplication (){ public static void main (String[] args) { SpringApplication.run(DemoApplication.class,args); } }
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。@Configuration是 Spring 3.0 添加的一个注解,用来代替 applicationContext.xml 配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在类来进行注册。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:用来代替配置文件中的 component-scan 配置,开启组件扫描,即自动扫描包路径下的 @Component 注解进行注册 bean 实例到 context 中。
Spring Boot会扫描主类所在的包路径下的注解。
2. 功能标签
标签名
含义
@Service
用于标注业务层组件,service层或者manager层
@Controller
用于标注控制层组件,action层
@Repository
用于标注数据访问组件,即DAO组件
@Component
用于泛指组件,不好归类时,我们选择这种组件
这个标签主要使用于标记这个接口的使用
通过写参数来放置不同组件之间的冲突
必须按照要求标记标签,便于注入框架
从前端接受的数据必须标记@ResponseBody
3. @Pointcut高级用法
3.1. 切点命名
一般情况下,切点是直接在增强方法处定义,这种切点的声明方法称为匿名切点。
切点命令例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class NamePointcut { @Pointcut("within(net.deniro.spring4.aspectj.*)") private void method1 () { } @Pointcut("within(net.deniro.spring4.aspectj.*)") protected void method2 () { } @Pointcut("method1() && method2()") public void method3 () { } }
3.2. 更多
切点
4. 元注解与实践
4.1. 元注解 @Retention
@Retention 可以用来修饰注解,是注解的注解,称为元注解。而这个决定注解保留的位置。
@Retention的注解中的RetentionPolicy有三个值:
RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
4.2. 元注解 @Target
注解作用的目标
1 2 3 4 5 6 7 8 @Target(ElementType.TYPE) @Target(ElementType.FIELD) @Target(ElementType.METHOD) @Target(ElementType.PARAMETER) @Target(ElementType.CONSTRUCTOR) @Target(ElementType.LOCAL_VARIABLE) @Target(ElementType.ANNOTATION_TYPE) @Target(ElementType.PACKAGE)
4.3. 元注解 @Document
标注这个注解将被包含在javadoc 中
4.4. 元注解 @Inherited
说明子类可以继承父类中的该注解
4.5. 元注解 @HandlesTypes
作用将注解指定的class对象作为参数传递到onStartup(ServeletContainerInitializer)方法中。
这个注解是留给用户进行扩展的
其指定的Class对象并没有要继承ServletContainerInitializer
也没有写入META-INF/services/的文件
问题:没有上述两种操作,Tomcat如何扫描到指定的类呢?
Byte Code Engineering Library(BCEL),这是字节码操纵框架的之一。
webConfig() 在调用processServletContainerInitializers()
时记录下注解的类名,然后在Step 4和Step 5中都来到processAnnotationsStream
这个方法,使用BCEL的ClassParser
在字节码层面读取了/WEB-INF/classes
和某些jar(应该可以在叫做fragments的概念中指定)中class文件的超类名和实现的接口名,判断是否与记录的注解类名相同,若相同再通过org.apache.catalina.util.Introspection
类load为Class对象,最后保存起来,于Step 11中交给org.apache.catalina.core.StandardContext
,也就是tomcat实际调用ServletContainerInitializer.onStartup()
的地方。
4.5.1. 参考
Java SPI、servlet3.0与@HandlesTypes源码分析
4.6. 元注解例子
1 2 3 4 5 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AnnatDemo{ public int value () ; }
以上代码定义了@AnnatDemo注解,作用目标是用于对方法注解,并且保留在运行时的环境中,我们可以利用反射获得一个方法上的注解调用定义的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface IClientProtocolEx extends IProtocol { int METHOD_START=0 ; @AnnatDemo(METHOD_START) public String say (String person) ; } Class ipt=IClientProtocalEx.class; Method[] mts=ipt.getMethod();for (Method mt:mts){ AnnatDemo ad=mt.getAnnotation(AnnatDemo.class); int value=ad.value(); System.out.println("value:" +value); }
5. 注解 @Slf4j
是用来省去打印日志所需要的重复代码
也就是如果不想写:private final Logger logger = LoggerFactory.getLogger(当前类名.class)
则可以使用注解@Slf4j
前置条件:需要添加lombok的依赖。
之后使用可以直接使用log.info(str)
来写入日志
简述参考
6. 注解 @AutoWired
6.1. @Autowired使用地点
1 @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
6.2. 参数的意义
1 2 require = ture 时,表示解析被标记的字段或方法,一定有对应的bean存在。require = false 时,表示解析被标记的字段或方法,没有对应的bean存在不会报错。
标签接口
1 2 3 4 5 6 7 public @interface Autowired { boolean required () default true ; }
6.3. @Autowired的原理
添加AutowiredAnnotationBeanPostProcessor
首先在执行AbstractApplicationContext#refresh()
方法时会执行obtainFreshBeanFactory()
方法,而这个方法执行时,会在
DefaultListableBeanFactory#beanDefinitionNames
数组中添加internalAutowiredAnnotationProcessor
。而internalAutowiredAnnotationProcessor
是和AutowiredAnnotationBeanPostProcessor
被一起注册到registerPostProcessor
中
1 2 3 4 5 if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition (AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); }
在项目启动的时候,自动执行refresh方法中的registerBeanPostProcessors(beanFactory)
1 2 3 4 5 6 7 8 9 10 protected void registerBeanPostProcessors (ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this ); }public static void registerBeanPostProcessors (ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) { String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true , false ); ... registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); ... }
getBeanNamesForType
是对前面加载的internalAutowiredAnnotationProcessor
进行转换成AutowiredAnnotationBeanPostProcessor
然后把返回值postProcessorNames
转为priorityOrderedPostProcessors
然后注册到registerBeanPostProcessors
中
1 2 3 4 5 6 private static void registerBeanPostProcessors ( ConfigurableListableBeanFactory beanFactory, List<BeanPostProcessor> postProcessors) { for (BeanPostProcessor postProcessor : postProcessors) { beanFactory.addBeanPostProcessor(postProcessor); } }
AbstractBeanFactory#beanPostProcessors
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void addBeanPostProcessor (BeanPostProcessor beanPostProcessor) { Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null" ); this .beanPostProcessors.remove(beanPostProcessor); if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { this .hasInstantiationAwareBeanPostProcessors = true ; } if (beanPostProcessor instanceof DestructionAwareBeanPostProcessor) { this .hasDestructionAwareBeanPostProcessors = true ; } this .beanPostProcessors.add(beanPostProcessor); }
6.4. AutowiredAnnotationBeanPostProcessor解析@Autowired
refresh()执行完registerBeanPostProcessors 方法后,继续执行finishBeanFactoryInitialization(beanFactory);
1 2 3 4 5 protected void finishBeanFactoryInitialization (ConfigurableListableBeanFactory beanFactory) { ... beanFactory.preInstantiateSingletons(); }
这里spring会创建需要的bean,一般在controller层中引入的service也会在此时依赖加载和创建
1 2 3 4 5 6 7 8 9 10 11 public void preInstantiateSingletons () throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this ); } for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); ... getBean(beanName); ... } }
在自定义的controller层时,会在getBean中执行autowire解析
1 2 3 4 5 6 7 8 9 10 11 12 13 public Object getBean (String name) throws BeansException { return doGetBean(name, null , null , false ); }protected <T> T doGetBean (final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { return createBean(beanName, mbd, args); }protected Object createBean (String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { ... doCreateBean(beanName, mbdToUse, args); ... }
doCreateBean是真正的bean实现,在创建的时候调用了applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 protected Object doCreateBean (final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { ... applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); ... }protected void applyMergedBeanDefinitionPostProcessors (RootBeanDefinition mbd, Class<?> beanType, String beanName) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof MergedBeanDefinitionPostProcessor) { MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp; bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName); } } }
getBeanPostProcessors
方法获取的就是上文中添加的AutowiredAnnotationBeanPostProcessor
的集合beanPostProcessors
。
则执行我们的想看到的AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
。
1 2 3 4 public void postProcessMergedBeanDefinition (RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null ); metadata.checkConfigMembers(beanDefinition); }
findAutowiringMetadata查询这个beanName中,是否有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private InjectionMetadata findAutowiringMetadata (String beanName, Class<?> clazz, @Nullable PropertyValues pvs) { ... metadata = buildAutowiringMetadata(clazz); ... }private InjectionMetadata buildAutowiringMetadata (final Class<?> clazz) { List<InjectionMetadata.InjectedElement> elements = new ArrayList <>(); Class<?> targetClass = clazz; AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod); if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { boolean required = determineRequiredStatus(ann); PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(new AutowiredMethodElement (method, required, pd)); } elements.addAll(0 , currElements); targetClass = targetClass.getSuperclass(); return new InjectionMetadata (clazz, elements); }
findAutowiredAnnotation(bridgedMethod)
找到这个类的autowire注解的类,添加到InjectionMetadata对象中。然后在checkConfigMembers方法中又注册到beanDefinition中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override public void postProcessMergedBeanDefinition (RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null ); metadata.checkConfigMembers(beanDefinition); }public void checkConfigMembers (RootBeanDefinition beanDefinition) { Set<InjectedElement> checkedElements = new LinkedHashSet <>(this .injectedElements.size()); for (InjectedElement element : this .injectedElements) { Member member = element.getMember(); if (!beanDefinition.isExternallyManagedConfigMember(member)) { beanDefinition.registerExternallyManagedConfigMember(member); checkedElements.add(element); if (logger.isTraceEnabled()) { logger.trace("Registered injected element on class [" + this .targetClass.getName() + "]: " + element); } } } this .checkedElements = checkedElements; }
6.5. @Autowired的注解应用
主要是使得其能够自动装配
1 2 3 4 5 private TbPositionDAO positionMapper; @Autowired public PositionServiceImpl (TbPositionDAO tbPositionDAO) { this .positionMapper = tbPositionDAO; }
7. @Component
@Component注解表明一个类会作为组件类,并告知spring要为这个类创建bean
8. @Bean
@Bean注解告诉Spring对象这个方法将会返回一个组成到Spring应用上下文中的bean,通常方法体中包含了最终产生bean实例的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class WireThirdLibClass { @Bean public ThirdLibClass getThirdLibClass () { return new ThirdLibClass (); } }@Bean public OneService getService (status) { case (status) { when 1 : return new serviceImpl1 (); when 2 : return new serviceImpl2 (); when 3 : return new serviceImpl3 (); } }
9. @Enableduling
9.1. 定时任务的作用
定时任务相当于闹钟,在什么时间做什么事情
9.2. 代码实现的方法
启动类里面使用@EnableScheduling注解开启功能,自动扫描
1 2 3 4 5 6 7 8 @SpringBootApplication @EnableScheduling public class MainApplication { public static void main (String[] args) { SpringApplication.run(MainApplication.class, args); } }
不同的注解的含义
1 2 3 4 5 6 @Scheduled(fixedDelay = 5000) @Scheduled(fixedRate = 3000) @Scheduled(cron = "0 0,30 0,8 ? * ? ")
9.3. fixedDelay
单位毫秒,指等业务结束之后在对应时间后执行
9.4. fixedRate
单位毫秒,指每多少毫秒执行一次
9.5. initialDelay
表示一个初识延迟时间,第一次被调用前延迟的时间
9.6. cron
比如你要设置每天什么时候执行,就可以用它,特殊的语法可以去查。
1 2 3 4 5 6 7 8 * 第一位,表示秒,取值0-59* 第二位,表示分,取值0-59* 第三位,表示小时,取值0-23* 第四位,日期天/日,取值1-31* 第五位,日期月份,取值1-12* 第六位,星期,取值1-7,星期一,星期二...,注:不是第1周,第二周的意思 另外:1表示星期天,2表示星期一。 * 第7为,年份,可以留空,取值1970-2099
部分的特殊时间
1 2 3 4 5 星号:可以理解为每的意思,每秒,每分,每天,每月,每年... 问号:问号只能出现在日期和星期这两个位置。 减号:表达一个范围,如在小时字段中使用"10-12" ,则表示从10 到12 点,即10 ,11 ,12 逗号:表达一个列表值,如在星期字段中使用"1,2,4" ,则表示星期一,星期二,星期四 斜杠:如:x/y,x是开始值,y是步长,比如在第一位 0 /15 就是,从0 秒开始,每15 秒,最后就是0 ,15 ,30 ,45 ,60 另:*/y,等同于0 /y
实例:
1 2 3 4 5 6 0 0 3 * * ? 每天3 点执行0 5 3 * * ? 每天3 点5 分执行0 5 3 ? * * 每天3 点5 分执行,与上面作用相同0 5 /10 3 * * ? 每天3 点的 5 分,15 分,25 分,35 分,45 分,55 分这几个时间点执行0 10 3 ? * 1 每周星期天,3 点10 分 执行,注:1 表示星期天 0 10 3 ? * 1# 3 每个月的第三个星期,星期天 执行,#号只能出现在星期的位置
9.7. 动态修改cron参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @Component @EnableScheduling @Transactional public class ReleasedTask implements SchedulingConfigurer { private Logger log = Logger.getLogger(getClass()); private TradeConfigMapper tradeConfigMapper; @Autowired private TradeConfigService configService; @Autowired private UserAccountService accountService; @Autowired private UserAccountDetailService accountDetailService; private String cron; @Autowired public ReleasedTask (TradeConfigMapper tradeConfigMapper) { this .tradeConfigMapper = tradeConfigMapper; String interval = tradeConfigMapper.getTimeInterval().getConfigValue(); this .cron = "0 0 3 */" + interval + " * ?" ; } @Override public void configureTasks (ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask(new Runnable () { @Override public void run () { String autoRelease = configService.getConfig(TradeConstants.TRADE_CONFIG_AUTO_RELEASE); if (autoRelease.equals("1" )) { log.info("任务执行---------" ); log.info("任务执行完---------" ); } } }, new Trigger () { @Override public Date nextExecutionTime (TriggerContext triggerContext) { CronTrigger trigger = new CronTrigger (cron); Date nextExec = trigger.nextExecutionTime(triggerContext); return nextExec; } }); } }
10. @Transactional
声明式事务有两种方式
在配置文件xml中做相关的事务规则声明
基于@Transactional注解来进行事务的声明
10.1. 利用该注解管理事务的实现
首先在xml配置文件中添加相应的事务配置信息,@EnableTransactionManagement注解来启用事务管理功能。
1 2 3 4 5 <tx:annotation-driven /> <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" ><property name ="dataSource" ref ="dataSource" /> </bean >
第二步:将@Transactional注解添加到合适的方法上,并设置合适的属性信息。
当然这个注解也可以被添加到类上
1 2 3 @Transactional(propagation= Propagation.SUPPORTS,readOnly=true) @Service(value ="employeeService") public class EmployeeService
属性名
说明
name
当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
propagation
事务的传播行为,默认值为 REQUIRED。
isolation
事务的隔离度,默认值采用 DEFAULT。
timeout
事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollback-for
用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for
抛出 no-rollback-for 指定的异常类型,不回滚事务。
10.2. Spring的注解方式的事务实现机制
Spring Framework默认使用AOP代理,根据@Transaction的属性配置信息,决定是否由拦截器TransactionInterceptor来使用拦截
如果触发拦截,那么会在目标方法开始执行前创建并加入事务,并且执行目标方法的逻辑,最后根据执行情况是否出现异常,利用抽象事务管理器AbstracPlatformTransactionManager操作数据源DataSourse提交或回滚事务。
详细的具体图解参照参考
10.3. 注解方式的事务使用注意事项
10.3.1. 正确配置propagation属性
需要注意下面三种 propagation 可以不启动事务。本来期望目标方法进行事务管理,但若是错误的配置这三种 propagation,事务将不会发生回滚。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
10.3.2. 正确的设置@Transactional 的 rollbackFor 属性
默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。
如果在事务中抛出其他类型的异常,并期望 Spring 能够回滚事务,可以指定 rollbackFor。例:
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)
通过分析 Spring 源码可以知道,若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。
10.3.3. @Transactional 只能应用到 public 方法才有效
只有@Transactional 注解应用到 public 方法,才能进行事务管理。这是因为在使用 Spring AOP 代理时,Spring 在调用在图 1 中的 TransactionInterceptor 在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取表 1. @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。
10.3.4. 避免 Spring 的 AOP 的自调用问题
在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。见清单 5 举例代码展示。
11. @ComponentScan
Spring容器会扫描@ComponentScan配置的包路径,找到其中的@Component注解的类加入到Spring容器中
12. @EnableAutoConfiguration
就是借助@Import的支持,收集和注册特定场景相关的Bean定义
1 2 3 4 5 6 7 8 9 10 11 @SuppressWarnings("deprecation") @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... }
12.1. @EnableScheduling
通过@Import将Spring调度框架相关的bean定义都加载到IoC容器。
12.2. @EnableMBeanExport
通过@Import将JMX相关的bean定义加载到IoC容器。
13. @Conditional
@Conditional
这个注解表示只有在所有指定条件匹配时, 组件才有资格进行注册。
@Conditional
可以通过以下任何方式使用:
在任何直接或间接使用@Component
和@Configuration
的类上作为一个类型注解使用
作为元注解
作为任何@Bean
方法的方法级注解
如果使用@Conditional
标记@Configuration
类, 则与该类关联的所有@Bean
方法, @Import
注解和@ComponentScan
注解都将受条件限制。
14. @Profile
14.1. 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ProfileCondition.class) public @interface Profile { String[] value(); }class ProfileCondition implements Condition { @Override public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata 这个玩意) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null ) { for (Object value : attrs.get("value" )) { if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) { return true ; } } return false ; } return true ; } }
在上面的matches类中:获取你以@Profile
注解配置的方法/类, 然后解析其中的value
值形成一个MultiValueMap
结构。
如果任何一个值通过acceptsProfiles
的验证, 则@Conditional(ProfileCondition.class)
成立
可通过applicationContext.getEnvironment().setActiveProfiles("chinese")
;设置配置, 也可以通过注解@ActiveProfiles(..)
设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Configuration public class AppConfig { @Profile("english") @Bean public English getEnglish () { return new English (); } @Profile("chinese") @Bean public Chinese getChinese () { return new Chinese (); } }class Chinese { }class English { }public class Test { public static void main (String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (); applicationContext.getEnvironment().setActiveProfiles("chinese" ); applicationContext.register(AppConfig.class); applicationContext.refresh(); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } }
如果要定义具有不通过Profile条件的备用bean,需要指定方法名
1 2 3 4 5 6 7 8 9 10 11 @Configuration public class ProfileDatabaseConfig { @Bean("dataSource") @Profile("development") public DataSource embeddedDatabase () { ... } @Bean("dataSource") @Profile("production") public DataSource productionDatabase () { ... } }
14.2. 描述
@Profile:Spring提供给我们根据当前的环境,动态的激活和切换一系列组件的功能
这个指定组件在哪个环境下才会被注册到容器中,不指定,任何环境下都能注册这个组件
环境|名称|数据源
开发环境|develop|/dev
测试环境|test|/test
生产环境|master|/master
加了环境标识的bean,只有在对应环境下才能被激活,默认为default环境
写在配置类上,只有是指定的环境,整个配置类里面的所有配置才能开始生效。
profile表达式支持一下操作符:
! 非
& 并
| 或
15. @ResponseBody 和 @ResponseStatus
@ResponseBody:典型spring mvc应用,请求点通常返回html页面。有时我们仅需要实际数据,如使用ajax请求。这时我们能通过@ResponseBody注解标记请求处理方法,审批人能够处理方法结果值作为http响应体。
@ResponseStatus:当请求点成功返回,spring提供http 200(ok)相应。如果请求点抛出异常,spring查找异常处理器,由其返回相应的http状态码。对这些方法增加@ResponseStatus注解,spring会返回自定义http状态码。
16. @ConfigurationProperties
在编写项目代码时,我们要求更灵活的配置,更好的模块化整合。在 Spring Boot 项目中,为满足以上要求,我们将大量的参数配置在application.properties
或application.yml
文件中,通过@ConfigurationProperties
注解,我们可以方便的获取这些参数值
16.1. 本地进行邮件发送模块测试
我们不想要使用本地进行邮件发送测试
16.1.1. 解决方法一:使用配置文件配置参数开关
配置参数开关,设计实现默认主题
1 2 3 4 myapp: mail: enabled: true default-subject: "This is a Test"
之后我们可以使用@Value
注解来访问这些属性,但是这个相对比较笨重。
16.1.2. 解决方法二:使用更安全的@ConfigurationProperties
注解
1 2 3 4 5 6 7 @Data @Component @ConfigurationProperties(prefix = "myapp.mail") public class mailModuleProperties { private Boolean enabled = Boolean.TRUE; private String defaultSubject; }
注意类的属性名称必须和外部属性的名称匹配
我们可以简单进行默认初始化
类本身可以是包私有的
类的字段必须有公共的setter方法
通过@Component
注解进行Bean注册
另一种注册方式,使用Spring的Java Configuration的特性注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration class PropertiesConfig { @Bean public MailModuleProperties mailModuleProperties () { return new MailModuleProperties (); } }@Configuration @EnableConfigurationProperties(MailModuleProperties.class) class PropertiesConfig { }
16.2. 属性无法转换问题
当属性不匹配的时候,运行时,Spring Boot会抛出异常
不希望启动失败:使用ignoreInvalidFields
字段,这时候如果没有的话,则会初始化对应属性为NULL
1 2 3 4 5 6 7 @Data @Component @ConfigurationProperties(prefix = "myapp.mail", ignoreInvalidFields = true) public class mailModuleProperties { private Boolean enabled = Boolean.TRUE; private String defaultSubject; }
16.3. 未知属性问题
如果我们在配置文件中多提供了一个没有注册的属性会怎样?配置文件如下:
1 2 3 4 5 myapp: mail: enabled: true default: "This is a Test" unkown-property: "foo"
Spring Boot默认会忽略没有绑定的属性,也不会运行报错,如果想要报错则使用ignoreUnknownFields
字段即可
1 2 3 4 5 6 7 @Data @Component @ConfigurationProperties(prefix = "myapp.mail", ignoreUnknownFields = false) public class mailModuleProperties { private Boolean enabled = Boolean.TRUE; private String defaultSubject; }
如上操作就会正常报错
16.4. 启动时校验属性
1 2 3 4 5 6 7 8 9 10 @Data @Validated @Component @ConfigurationProperties(prefix = "myapp.mail") public class mailModuleProperties { @NotNull private Boolean enabled = Boolean.TRUE; @NotEmpty private String defaultSubject; }
在启动的时候,则会按照标签进行检查,如果不符合则会报BindValidationException
异常
16.5. 复杂类型的属性
16.5.1. List类型
1 2 3 4 5 6 @Data @Component @ConfigurationProperties(prefix = "myapp.mail") public class mailModuleProperties { private List<String> smtpServers; }
application.properties方式填充
1 2 myapp.mail.smtpServers[0]=server1 myapp.mail.smtpServers[1]=server2
使用yml的方式进行填充
1 2 3 4 5 myapp: mail: smtp-servers: - server1 - server2
16.5.2. Duration类型
1 2 3 4 5 6 7 8 @Data @Component @ConfigurationProperties(prefix = "myapp.mail") public class mailModuleProperties { @DurationUnit(ChronoUnit.SECONDS) private Duration pauseBetweenMails; }
默认单位毫秒
application.properties方式填充
1 myapp.mail.pause-between-mails=5s
使用yml的方式进行填充
1 2 3 myapp: mail: pause-between-mails: 5s
单位
英文
中文
ns
nanoseconds
纳秒
us
microseconds
微秒
ms
milliseconds
毫秒
s
seconds
秒
m
minutes
分
h
hours
时
d
days
天
16.5.3. DataSize类型
类似Duration,默认单位byte(字节)
1 2 3 4 5 6 7 8 @Data @Component @ConfigurationProperties(prefix = "myapp.mail") public class mailModuleProperties { @DataSizeUnit(DataUnit.MEGABYTES) private DataSize maxAttachmentSize = DataSize.ofMegabytes(2 ); }
默认单位毫秒
application.properties方式填充
1 myapp.mail.max-attachment-size=1MB
使用yml的方式进行填充
1 2 3 myapp: mail: max-attachment-size: 1MB
单位
英文
B
bytes
KB
kilobytes
MB
megabytes
GB
gigabytes
TB
terabytes
16.5.4. 自定义类型
我们想要配置自己定义的类型
比如配置文件如下
1 2 3 myapp: mail: max-attachment-weight: 5kg
添加Weight属性
1 2 3 4 5 6 @Data @Component @ConfigurationProperties(prefix = "myapp.mail") public class mailModuleProperties { private Weight maxAttachmentWeight; }
创建自己的转换器
1 2 3 4 5 6 7 class WeightConverter implements Converter <String, Weight> { @Override public Weight convert (String source) { } }
绑定到Spring上下文
1 2 3 4 5 6 7 8 @Configuration class PropertiesConfig { @Bean @ConfigurationPropertiesBinding public WeightConverter weightConverter () { return new WeightConverter (); } }
16.6. 自动补全:Spring Boot Configuration Processor
Maven依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency >
之后重新build后,在/target/classed/META-INF中可以找到一个JSON文件,提供自动提示
17. @Value注解
@Value是不通过配置文件注入属性的一种方式
可以注入如下部分
17.1. 注入普通字符串
1 2 3 @Value("normal") private String normal;
17.2. 注入操作系统属性
1 2 3 @Value("#{systemProperties['os.name']}") private String systemPropertiesName;
17.3. 注入表达式结果
1 2 3 @Value("#{ T(java.lang.Math).random() * 100.0 }") private double randomNumber;
17.4. 注入其他Bean属性:
注入beanInject对象的属性another
1 2 3 @Value("#{beanInject.another}") private String fromAnotherBean;
BeanInject类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component public class BeanInject { @Value("其他Bean的属性") private String another; public String getAnother () { return another; } public void setAnother (String another) { this .another = another; } }
17.5. 注入文件资源
1 2 3 @Value("classpath:com/hry/spring/configinject/config.txt") private Resource resourceFile;
17.6. 注入URL资源
1 2 3 @Value("http://www.baidu.com") private Resource testUrl;
17.7. 注入配置文件的属性
17.7.1. 配置文件分类
默认配置文件:application.properties
或application.yml
文件
自定义配置文件:需要通过@PropertySource
加载
如果有多个配置文件,第一个属性文件和第二个属性文件存在相同Key,则最后一个属性文件的key启作用
配置文件的路径也可以从变量中加载,如下面的例子
17.7.2. 读取配置文件实例
第一个配置文件:config.properties
,如下
1 2 book.name=bookName anotherfile.configinject=placeholder
第二个配置文件:config_placeholder.properties
,如下
1 book.name.placeholder=bookNamePlaceholder
代码部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Component @PropertySource({"classpath:com/hry/spring/configinject/config.properties", "classpath:com/hry/spring/configinject/config_${anotherfile.configinject}.properties"}) public class ConfigurationFileInject { @Value("${app.name}") private String appName; @Value("${book.name}") private String bookName; @Value("${book.name.placeholder}") private String bookNamePlaceholder; @Autowired private Environment env; public String toString () { StringBuilder sb = new StringBuilder (); sb.append("bookName=" ).append(bookName).append("\r\n" ) .append("bookNamePlaceholder=" ).append(bookNamePlaceholder).append("\r\n" ) .append("appName=" ).append(appName).append("\r\n" ) .append("env=" ).append(env).append("\r\n" ) .append("env=" ).append(env.getProperty("book.name.placeholder" )).append("\r\n" ); return sb.toString(); } }
18. 参考
Spring中@Component与@Bean的区别
四种元注释
Springboot中@Autowired的原理解析
SpringBoot定时任务@EnableScheduling
spring的定时任务,超级简单好用
两种定时器的实现
透彻的掌握 Spring 中@transactional 的使用
@Profile注解详解
Spring @Profile 注解介绍
@ConfigurationProperties 注解使用姿势,这一篇就够了
Spring Boot系列四 Spring @Value 属性注入使用总结一