首先,在 Spring 4.X 之后(不用 Spring Boot 的话)使用注释需要添加 aop 依赖。虽然不需要这么做了,还是有助于了解 Spring Boot 到底为我们做了什么。
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>{springframework.version}</version>
</dependency>
而且需要在 XML 中添加约束并在 context 中配置扫描范围。
<?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"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
	<context:annotation-config/>
</beans>
配置扫描范围
<!--指定注解扫描包-->
<context:component-scan base-package="com.yourpackage"/>
接下来按类别整理一些最常用的注释。
Bean 的扫描
@ComponentScan
- 
@ComponentScan:通过注释方式配置扫描范围,将其下的@Component组件(包括@Controller、@Service、@Repository)纳入 IOC 容器. 只能作用于配置类,且 Spring Boot 的入口类不能被纳入到扫描范围中.Spring Boot 默认的扫描范围是启动类所在包开始,当前包及子包下的所有文件
@Configuration @ComponentScan("cc.mrbird.demo") public class WebConfig { }可以通过
excludeFilters来排除一些组件的扫描,通过@Filter注释完成@Configuration @ComponentScan(value = "cc.mrbird.demo", excludeFilters = { // 将注解为 Controller 和 Repository 的类排除 @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Repository.class}), // 排除所有 User 类(及子类、实现类) @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = User.class) }) public class WebConfig { }如上所示,可以跟据注释或直接指定排除相应类型(包括其子类、实现类)
includeFilters的作用和excludeFilters相反,其指定的是哪些组件需要被扫描:@Configuration @ComponentScan(value = "cc.mrbird.demo", includeFilters = { // 仅纳入注释为 Service 的类 @Filter(type = FilterType.ANNOTATION, classes = Service.class) }, useDefaultFilters = false) public class WebConfig { }通过实现
org.springframework.core.type.filter.TypeFilter接口可以自定义扫描策略,通过实现match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)方法,返回true说明匹配成功。 
Bean 的注册
@Bean,@Component
- 
@Bean:通过注解向 IOC 容器注册默认为方法名的 Bean,也可以通过@Bean("{name}")来重新命名@Configuration public class WebConfig { @Bean() public User user() { return new User("mrbirdy", 18); } }实现了
FactoryBean<T>接口的 Bean 是一类特殊的 Beanpublic class CherryFactoryBean implements FactoryBean<Cherry> { @Override public Cherry getObject() { return new Cherry(); } @Override public Class<?> getObjectType() { return Cherry.class; } @Override public boolean isSingleton() { return false; } }如果
isSingleton()为false,则每次会调用getObject()从中获取 Bean。通过加上前缀
&从工厂中取出对应的 BeanObject cherryFactoryBean = context.getBean("&cherryFactoryBean"); - 
@Component:component-scan指定的扫描路径下所有被@Controller、@Service、@Repository和@Component注解标注的类都会被纳入IOC容器中@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { public String name = ... }说明该类被Spring管理。
Component类的有参构造方法会被默认用作依赖注入,所以相比在成员变量上加@Autowire来注入依赖,更合适的方法是通过构造方法注入。衍生注解:按照MVC三层架构分层
@Repository:用于DAO层,数据库操作@Service:用于Service层,复杂逻辑@Controller:用于Controller层,接收用户请求并调用Service层返回数据
连同
@Component,四个注解功能一样,都代表将某个类注册到Spring中 
Bean 的加载
@Scope,@Lazy,@Conditional
- 
@Scope:改变组件的作用域(默认singleton)singleton:单实例(默认),在Spring IOC容器启动的时候会调用方法创建对象然后纳入到IOC容器中,以后每次获取都是直接从IOC容器中获取(map.get());prototype:多实例,IOC容器启动的时候并不会去创建对象,而是在每次获取的时候才会去调用方法创建对象;request:一个请求对应一个实例;session:同一个session对应一个实例。
 - 
@Lazy:懒加载(针对singleton)懒加载的单例不会马上调用方法创建对象并注册,只有当第一次被使用时才会调用方法创建对象并加入容器中。
 - 
@Conditional:条件加载,类似于前面@ComponentScan中的Filter使用
@Conditional注解我们可以指定组件注册的条件,即满足特定条件才将组件纳入到 IOC 容器中。在使用该注解之前,我们需要创建一个类,实现
Condition接口:public class MyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return false; } }该接口包含一个
matches方法,包含两个入参:ConditionContext:上下文信息;AnnotatedTypeMetadata:注解信息。
简单完善一下这个实现类:
public class MyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String osName = context.getEnvironment().getProperty("os.name"); return osName != null && osName.contains("Windows"); } }接着将这个条件添加到User Bean注册的地方:
@Bean @Conditional(MyCondition.class) public User user() { return new User("mrbird", 18); }在Windows环境下,User这个组件将被成功注册,如果是别的操作系统,这个组件将不会被注册到IOC容器中。
 
属性注入
@Value,@ConfigurationProperties,@PropertySource
- 
@Value: Property注入可以直接用在成员变量上,也可以用在Setter上
需要注意的是
@value这种方式是不被推荐的,Spring 比较建议的是下面几种读取配置信息的方式。 - 
@ConfigurationProperties: Properties 读取并与 bean 绑定LibraryProperties类上加了@Component注解,我们可以像使用普通 bean 一样将其注入到类中使用。import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import java.util.List; @Component @ConfigurationProperties(prefix = "library") class LibraryProperties { private String location; private List<Book> books; static class Book { String name; String description; } }相应的配置文件内容
library: location: 湖北武汉加油中国加油 books: - name: 天才基本法 description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。 - name: 时间的秩序 description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。 - name: 了不起的我 description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻?然后就可以通过
private final LibraryProperties library注入 Property 对象了。题外话:
InitializingBean接口下的afterPropertiesSet()方法可以作为一个 Property 注入后的 AOP 使用,如下所示:@SpringBootApplication public class ReadConfigPropertiesApplication implements InitializingBean { private final LibraryProperties library; public ReadConfigPropertiesApplication(LibraryProperties library) { this.library = library; } public static void main(String[] args) { SpringApplication.run(ReadConfigPropertiesApplication.class, args); } @Override public void afterPropertiesSet() { System.out.println(library.getLocation()); System.out.println(library.getBooks()); } }如果 Property类上不加
Component,就需要在 SpringBootApplication 上加@EnableConfigurationProperties来注册 Bean ,如下所示:@SpringBootApplication @EnableConfigurationProperties(ProfileProperties.class) public class ReadConfigPropertiesApplication implements InitializingBean { private final ProfileProperties profileProperties; public ReadConfigPropertiesApplication(ProfileProperties profileProperties) { this.profileProperties = profileProperties; } public static void main(String[] args) { SpringApplication.run(ReadConfigPropertiesApplication.class, args); } @Override public void afterPropertiesSet() { System.out.println(profileProperties.toString()); } } - 
@PropertySource: 有单独文件的 properties 可以通过@PropertySource来读取import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Component @PropertySource("classpath:website.properties") class WebSite { @Value("${url}") private String url; } 
校验注解
这里可以参考 Spring Boot 指南
JSR 提供的校验注解:
@Null被注释的元素必须为 null@NotNull被注释的元素必须不为 null@AssertTrue被注释的元素必须为 true@AssertFalse被注释的元素必须为 false@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值@Size(max=, min=)被注释的元素的大小必须在指定的范围内@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内@Past被注释的元素必须是一个过去的日期@Future被注释的元素必须是一个将来的日期@Pattern(regex=,flag=)被注释的元素必须符合指定的正则表达式
public class Person {
    @NotNull(message = "classId 不能为空")
    private String classId;
    @Size(max = 33)
    @NotNull(message = "name 不能为空")
    private String name;
    @Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围")
    @NotNull(message = "sex 不能为空")
    private String sex;
    @Email(message = "email 格式不正确")
    @NotNull(message = "email 不能为空")
    private String email;
}
Hibernate Validator 提供的校验注解:
@NotBlank(message =)验证字符串非 null,且长度必须大于 0@Email被注释的元素必须是电子邮箱地址@Length(min=,max=)被注释的字符串的大小必须在指定的范围内@NotEmpty被注释的字符串的必须非空@Range(min=,max=,message=)被注释的元素必须在合适的范围内
自动装配
@Autowired,@Resource
@Autowired
自动装配,先byType再byName,如果不能唯一自动装配,则需要@Qualifier(value="xxx").
在成员变量上实现:
public class User {
   @Autowired
   private Cat cat;
   @Autowired
   private Dog dog;
   private String str;
   public Cat getCat() {
       return cat;
  }
   public Dog getDog() {
       return dog;
  }
   public String getStr() {
       return str;
  }
}
然后在XML中配置Bean
<context:annotation-config/>
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>
- 
@Qualifer():如果bean名字不为类的默认名字,则要加@Qualifer@Autowired @Qualifier(value = "cat2") private Cat cat; @Autowired @Qualifier(value = "dog2") private Dog dog; 
  
</br>
  
- #### `@Resource`
  自动装配,先`byName`再`byType`,如果`name`属性指定,则只会按照名称进行装配.
  默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配
  ```java
  public class User {
     //如果允许对象为null,设置required = false,默认为true
     @Resource(name = "cat2")
     private Cat cat;
     @Resource
     private Dog dog;
     private String str;
  }
事务
@Transactional
- 
@Transactional在 Service 的实现类中使用,将方法标注为 SQL 事务.
首先需要在入口类上加入
@EnableTransactionManagement注解以开启事务:@EnableTransactionManagement @SpringBootApplication public class TransactionApplication { public static void main(String[] args) throws Exception { SpringApplication.run(TransactionApplication.class, args); } }然后再
@Service中标注事务:@Service public class UserServiceImpl implements UserService { private final UserMapper userMapper; public UserServiceImpl(UserMapper userMapper) { this.userMapper = userMapper; } @Transactional @Override public void saveUser(User user) { userMapper.save(user); // 测试事务回滚 if (!StringUtils.hasText(user.getUsername())) { throw new RuntimeException("username不能为空"); } } }如果生效,当用户名为空(这里用的是
org.springframework.util包下的hasText()方法,要求字符串不为null、长度大于0、不全为空),则会捕获到异常而进行回滚。@Transactional同样利用的是 Spring 的 AOP 机制, 这里有两个坑.注意点一
如果抛出的异常不是
RuntimeException或者Error,也不是@Transactional注解指定的回滚异常类型,则不会进行事务回滚。所以在自定义需要回滚的异常时,要么继承
RuntimeException,要么直接在注释上标出来:@Transactional(rollbackFor = Exception.class) @Override public void saveUser(User user) throws Exception { userMapper.save(user); // 测试事务回滚 if (!StringUtils.hasText(user.getUsername())) { throw new Exception("username不能为空"); } }注意点二
如果我们在相同
Service下的非事务方法中,对事务方法进行调用,事务同样不会生效。如下:@Service public class UserServiceImpl implements UserService { private final UserMapper userMapper; public UserServiceImpl(UserMapper userMapper) { this.userMapper = userMapper; } @Override public void saveUserTest(User user) { this.saveUser(user); } @Transactional @Override public void saveUser(User user) { userMapper.save(user); // 测试事务回滚 if (!StringUtils.hasText(user.getUsername())) { throw new ParamInvalidException("username不能为空"); } } }因为 Spring 事务控制通过 AOP 代理实现,通过代理目标对象来增强目标方法,而如果用
this调用方法,this绕过了代理类(实际上是代理类绕过原类,this无视了代理类),直接用了类本身,从而没有触发事务。要让代理类重新生效有两种方法
1、 从 IOC 中获取 Bean 后再调用:
@Override public void saveUserTest(User user) { UserService userService = context.getBean(UserService.class); userService.saveUser(user); }2、 直接从 AOP 上下文取代理对象进行调用(需要引入 AOP Starter 依赖),且需要再在 SpringBoot 入口类中通过注解
@EnableAspectJAutoProxy(exposeProxy = true)将当前代理对象暴露到 AOP 上下文中(通过AopContext的ThreadLocal实现)@Override public void saveUserTest(User user) { UserService userService = (UserService) AopContext.currentProxy(); userService.saveUser(user); }总之是有种为了舍近求远又额外兜了一大圈的感觉,个人认为写事务就不要代入编程优雅方面的考虑了,没必要在方法单一职责上那么较真。