2-Spring Boot 核心注解

1. 核心注解

  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);
}
}
  1. @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。@Configuration是 Spring 3.0 添加的一个注解,用来代替 applicationContext.xml 配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在类来进行注册。
  2. @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
  3. @ComponentScan:用来代替配置文件中的 component-scan 配置,开启组件扫描,即自动扫描包路径下的 @Component 注解进行注册 bean 实例到 context 中。
  4. Spring Boot会扫描主类所在的包路径下的注解。

2. 功能标签

标签名 含义
@Service 用于标注业务层组件,service层或者manager层
@Controller 用于标注控制层组件,action层
@Repository 用于标注数据访问组件,即DAO组件
@Component 用于泛指组件,不好归类时,我们选择这种组件
  1. 这个标签主要使用于标记这个接口的使用
  2. 通过写参数来放置不同组件之间的冲突
  3. 必须按照要求标记标签,便于注入框架
  4. 从前端接受的数据必须标记@ResponseBody

3. @Pointcut高级用法

3.1. 切点命名

  1. 一般情况下,切点是直接在增强方法处定义,这种切点的声明方法称为匿名切点。
  2. 切点命令例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class NamePointcut {
/**
* 切点被命名为 method1,且该切点只能在本类中使用
*/
@Pointcut("within(net.deniro.spring4.aspectj.*)")
private void method1() {
}
/**
* 切点被命名为 method2,且该切点可以在本类或子孙类中使用
*/
@Pointcut("within(net.deniro.spring4.aspectj.*)")
protected void method2() {
}
/**
* 切点被命名为 method3,且该切点可以在任何类中使用
* 这里还使用了复合运算
*/
@Pointcut("method1() && method2()")
public void method3() {
}
}

3.2. 更多

切点

4. 元注解与实践

4.1. 元注解 @Retention

  1. @Retention可以用来修饰注解,是注解的注解,称为元注解。而这个决定注解保留的位置。
  2. @Retention的注解中的RetentionPolicy有三个值:
    1. RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
    2. RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
    3. RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

4.2. 元注解 @Target

  1. 注解作用的目标
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

  1. 标注这个注解将被包含在javadoc

4.4. 元注解 @Inherited

  1. 说明子类可以继承父类中的该注解

4.5. 元注解 @HandlesTypes

  1. 作用将注解指定的class对象作为参数传递到onStartup(ServeletContainerInitializer)方法中。
  2. 这个注解是留给用户进行扩展的
    1. 其指定的Class对象并没有要继承ServletContainerInitializer
    2. 也没有写入META-INF/services/的文件
  3. 问题:没有上述两种操作,Tomcat如何扫描到指定的类呢?
    1. Byte Code Engineering Library(BCEL),这是字节码操纵框架的之一。
    2. 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();
}
  1. 以上代码定义了@AnnatDemo注解,作用目标是用于对方法注解,并且保留在运行时的环境中,我们可以利用反射获得一个方法上的注解调用定义的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//比如@AnnatDemo作用于以下方法:
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);//如果方法上 没有该注解 则返回null
int value=ad.value();
  System.out.println("value:"+value);
}

5. 注解 @Slf4j

  1. 是用来省去打印日志所需要的重复代码
  2. 也就是如果不想写:private final Logger logger = LoggerFactory.getLogger(当前类名.class)则可以使用注解@Slf4j
  3. 前置条件:需要添加lombok的依赖。
  4. 之后使用可以直接使用log.info(str)来写入日志
  5. 简述参考

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. 标签接口
1
2
3
4
5
6
7
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
*/
boolean required() default true;
}

6.3. @Autowired的原理

  1. 添加AutowiredAnnotationBeanPostProcessor
  2. 首先在执行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));
}
  1. 在项目启动的时候,自动执行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);
...
}
  1. 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);
}
}
  1. 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");
// Remove from old position, if any
this.beanPostProcessors.remove(beanPostProcessor);
// Track whether it is instantiation/destruction aware
if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
this.hasInstantiationAwareBeanPostProcessors = true;
}
if (beanPostProcessor instanceof DestructionAwareBeanPostProcessor) {
this.hasDestructionAwareBeanPostProcessors = true;
}
// Add to end of list
this.beanPostProcessors.add(beanPostProcessor);
}

6.4. AutowiredAnnotationBeanPostProcessor解析@Autowired

  1. refresh()执行完registerBeanPostProcessors 方法后,继续执行finishBeanFactoryInitialization(beanFactory);
1
2
3
4
5
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
...
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
  1. 这里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);
...
}
}
  1. 在自定义的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);
...
}
  1. 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);
}
}
}
  1. 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);
}
  1. 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);
}
  1. 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. 主要是使得其能够自动装配
1
2
3
4
5
private TbPositionDAO positionMapper;
@Autowired
public PositionServiceImpl(TbPositionDAO tbPositionDAO) {
this.positionMapper = tbPositionDAO;
}

7. @Component

  1. @Component注解表明一个类会作为组件类,并告知spring要为这个类创建bean

8. @Bean

  1. @Bean注解告诉Spring对象这个方法将会返回一个组成到Spring应用上下文中的bean,通常方法体中包含了最终产生bean实例的逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Component和Bean可以通用的
public class WireThirdLibClass {
@Bean
public ThirdLibClass getThirdLibClass() {
return new ThirdLibClass();
}
}
//只能使用Bean
@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. 定时任务的作用

  1. 定时任务相当于闹钟,在什么时间做什么事情

9.2. 代码实现的方法

  1. 启动类里面使用@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. 不同的注解的含义
1
2
3
4
5
6
//表示上一个方法结束后5秒
@Scheduled(fixedDelay = 5000)
//表示每隔3秒
@Scheduled(fixedRate = 3000)
//表示每天8时30分0秒执行
@Scheduled(cron = "0 0,30 0,8 ? * ? ")

9.3. fixedDelay

  1. 单位毫秒,指等业务结束之后在对应时间后执行

9.4. fixedRate

  1. 单位毫秒,指每多少毫秒执行一次

9.5. initialDelay

  1. 表示一个初识延迟时间,第一次被调用前延迟的时间

9.6. cron

  1. 比如你要设置每天什么时候执行,就可以用它,特殊的语法可以去查。
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. 部分的特殊时间
1
2
3
4
5
(*)星号:可以理解为每的意思,每秒,每分,每天,每月,每年...
(?)问号:问号只能出现在日期和星期这两个位置。
(-)减号:表达一个范围,如在小时字段中使用"10-12",则表示从1012点,即10,11,12
(,)逗号:表达一个列表值,如在星期字段中使用"1,2,4",则表示星期一,星期二,星期四
(/)斜杠:如:x/y,x是开始值,y是步长,比如在第一位(秒) 0/15就是,从0秒开始,每15秒,最后就是015304560 另:*/y,等同于0/y
  1. 实例:
1
2
3
4
5
6
0 0 3 * * ?     每天3点执行
0 5 3 * * ? 每天35分执行
0 5 3 ? * * 每天35分执行,与上面作用相同
0 5/10 3 * * ? 每天3点的 5分,15分,25分,35分,45分,55分这几个时间点执行
0 10 3 ? * 1 每周星期天,310分 执行,注: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 + " * ?";
//cron = "*/10 * * * * ?";
}

@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

  1. 声明式事务有两种方式
    1. 在配置文件xml中做相关的事务规则声明
    2. 基于@Transactional注解来进行事务的声明

10.1. 利用该注解管理事务的实现

  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>
  1. 第二步:将@Transactional注解添加到合适的方法上,并设置合适的属性信息。
    1. 当然这个注解也可以被添加到类上
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的注解方式的事务实现机制

  1. Spring Framework默认使用AOP代理,根据@Transaction的属性配置信息,决定是否由拦截器TransactionInterceptor来使用拦截
  2. 如果触发拦截,那么会在目标方法开始执行前创建并加入事务,并且执行目标方法的逻辑,最后根据执行情况是否出现异常,利用抽象事务管理器AbstracPlatformTransactionManager操作数据源DataSourse提交或回滚事务。
  3. 详细的具体图解参照参考

10.3. 注解方式的事务使用注意事项

10.3.1. 正确配置propagation属性

  1. 需要注意下面三种 propagation 可以不启动事务。本来期望目标方法进行事务管理,但若是错误的配置这三种 propagation,事务将不会发生回滚。
    1. TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    2. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    3. TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

10.3.2. 正确的设置@Transactional 的 rollbackFor 属性

  1. 默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。
  2. 如果在事务中抛出其他类型的异常,并期望 Spring 能够回滚事务,可以指定 rollbackFor。例:
    • @Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)
  3. 通过分析 Spring 源码可以知道,若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

10.3.3. @Transactional 只能应用到 public 方法才有效

  1. 只有@Transactional 注解应用到 public 方法,才能进行事务管理。这是因为在使用 Spring AOP 代理时,Spring 在调用在图 1 中的 TransactionInterceptor 在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取表 1. @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

10.3.4. 避免 Spring 的 AOP 的自调用问题

  1. 在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。见清单 5 举例代码展示。

11. @ComponentScan

  1. Spring容器会扫描@ComponentScan配置的包路径,找到其中的@Component注解的类加入到Spring容器中

12. @EnableAutoConfiguration

  1. 就是借助@Import的支持,收集和注册特定场景相关的Bean定义
1
2
3
4
5
6
7
8
9
10
11
//@EnableAutoConfiguration的复合注解情况
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

12.1. @EnableScheduling

  1. 通过@Import将Spring调度框架相关的bean定义都加载到IoC容器。

12.2. @EnableMBeanExport

  1. 通过@Import将JMX相关的bean定义加载到IoC容器。

13. @Conditional

  1. @Conditional这个注解表示只有在所有指定条件匹配时, 组件才有资格进行注册。
  2. @Conditional可以通过以下任何方式使用:
    1. 在任何直接或间接使用@Component@Configuration的类上作为一个类型注解使用
    2. 作为元注解
    3. 作为任何@Bean方法的方法级注解
  3. 如果使用@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();
}
//profileCondtion.class
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata 这个玩意) {
// metadata 这个玩意是你以注解方式配置的Spring的、尚未被IOC容器处理的内容 (又分AnnotationMetadata和MethodMetadata 说多了)
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;
}
}
  1. 在上面的matches类中:获取你以@Profile注解配置的方法/类, 然后解析其中的value值形成一个MultiValueMap结构。
  2. 如果任何一个值通过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); // 这里你可以看看打印的bean是否和你想的一样
}
}
}
  1. 如果要定义具有不通过Profile条件的备用bean,需要指定方法名
1
2
3
4
5
6
7
8
9
10
11
//TODO
@Configuration
public class ProfileDatabaseConfig {
@Bean("dataSource")
@Profile("development")
public DataSource embeddedDatabase() { ... }

@Bean("dataSource")
@Profile("production")
public DataSource productionDatabase() { ... }
}

14.2. 描述

  1. @Profile:Spring提供给我们根据当前的环境,动态的激活和切换一系列组件的功能
  2. 这个指定组件在哪个环境下才会被注册到容器中,不指定,任何环境下都能注册这个组件

环境|名称|数据源
开发环境|develop|/dev
测试环境|test|/test
生产环境|master|/master

  1. 加了环境标识的bean,只有在对应环境下才能被激活,默认为default环境
  2. 写在配置类上,只有是指定的环境,整个配置类里面的所有配置才能开始生效。
  3. profile表达式支持一下操作符:
    1. ! 非
    2. & 并
    3. | 或

15. @ResponseBody 和 @ResponseStatus

  1. @ResponseBody:典型spring mvc应用,请求点通常返回html页面。有时我们仅需要实际数据,如使用ajax请求。这时我们能通过@ResponseBody注解标记请求处理方法,审批人能够处理方法结果值作为http响应体。
  2. @ResponseStatus:当请求点成功返回,spring提供http 200(ok)相应。如果请求点抛出异常,spring查找异常处理器,由其返回相应的http状态码。对这些方法增加@ResponseStatus注解,spring会返回自定义http状态码。

16. @ConfigurationProperties

  1. 在编写项目代码时,我们要求更灵活的配置,更好的模块化整合。在 Spring Boot 项目中,为满足以上要求,我们将大量的参数配置在application.propertiesapplication.yml文件中,通过@ConfigurationProperties注解,我们可以方便的获取这些参数值

16.1. 本地进行邮件发送模块测试

  1. 我们不想要使用本地进行邮件发送测试

16.1.1. 解决方法一:使用配置文件配置参数开关

  1. 配置参数开关,设计实现默认主题
1
2
3
4
myapp:
mail:
enabled: true
default-subject: "This is a Test"
  1. 之后我们可以使用@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;
}
  1. 注意类的属性名称必须和外部属性的名称匹配
  2. 我们可以简单进行默认初始化
  3. 类本身可以是包私有的
  4. 类的字段必须有公共的setter方法
  5. 通过@Component注解进行Bean注册
  6. 另一种注册方式,使用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. 属性无法转换问题

  1. 当属性不匹配的时候,运行时,Spring Boot会抛出异常
  2. 不希望启动失败:使用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. 如果我们在配置文件中多提供了一个没有注册的属性会怎样?配置文件如下:
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;
}
  1. application.properties方式填充
1
2
myapp.mail.smtpServers[0]=server1
myapp.mail.smtpServers[1]=server2
  1. 使用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;
}

默认单位毫秒

  1. application.properties方式填充
1
myapp.mail.pause-between-mails=5s
  1. 使用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类型

  1. 类似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);
}

默认单位毫秒

  1. application.properties方式填充
1
myapp.mail.max-attachment-size=1MB
  1. 使用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. 比如配置文件如下
1
2
3
myapp:
mail:
max-attachment-weight: 5kg
  1. 添加Weight属性
1
2
3
4
5
6
@Data
@Component
@ConfigurationProperties(prefix = "myapp.mail")
public class mailModuleProperties{
private Weight maxAttachmentWeight;
}
  1. 创建自己的转换器
1
2
3
4
5
6
7
class WeightConverter implements Converter<String, Weight> {

@Override
public Weight convert(String source){
//从字符串转换成Weight对象
}
}
  1. 绑定到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

  1. Maven依赖
1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
  1. 之后重新build后,在/target/classed/META-INF中可以找到一个JSON文件,提供自动提示

17. @Value注解

  1. @Value是不通过配置文件注入属性的一种方式
  2. 可以注入如下部分

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属性:

  1. 注入beanInject对象的属性another
1
2
3
// 注入其他Bean属性:注入beanInject对象的属性another,类具体定义见下面
@Value("#{beanInject.another}")
private String fromAnotherBean;
  1. BeanInject类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//beanInject类
@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
// 注入URL资源
@Value("http://www.baidu.com")
private Resource testUrl;

17.7. 注入配置文件的属性

17.7.1. 配置文件分类

  1. 默认配置文件:application.propertiesapplication.yml文件
  2. 自定义配置文件:需要通过@PropertySource加载
    • 如果有多个配置文件,第一个属性文件和第二个属性文件存在相同Key,则最后一个属性文件的key启作用
    • 配置文件的路径也可以从变量中加载,如下面的例子

17.7.2. 读取配置文件实例

  1. 第一个配置文件:config.properties,如下
1
2
book.name=bookName
anotherfile.configinject=placeholder
  1. 第二个配置文件:config_placeholder.properties,如下
1
book.name.placeholder=bookNamePlaceholder
  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
25
26
27
28
29
@Component
// 引入外部配置文件组:${app.configinject}的值来自config.properties。
// 如果相同,这里使用了配置文件的属性
@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; // 这里的值来自application.properties,spring boot启动时默认加载此文件

@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")
// 从eniroment中获取属性值
.append("env=").append(env.getProperty("book.name.placeholder")).append("\r\n");
return sb.toString();
}
}

18. 参考

  1. Spring中@Component与@Bean的区别
  2. 四种元注释
  3. Springboot中@Autowired的原理解析
  4. SpringBoot定时任务@EnableScheduling
  5. spring的定时任务,超级简单好用
  6. 两种定时器的实现
  7. 透彻的掌握 Spring 中@transactional 的使用
  8. @Profile注解详解
  9. Spring @Profile 注解介绍
  10. @ConfigurationProperties 注解使用姿势,这一篇就够了
  11. Spring Boot系列四 Spring @Value 属性注入使用总结一

2-Spring Boot 核心注解
https://spricoder.github.io/2022/04/13/Spring-Boot/2-Spring-Boot-%E6%A0%B8%E5%BF%83%E6%B3%A8%E8%A7%A3/
作者
SpriCoder
发布于
2022年4月13日
许可协议