Spring Boot 学习笔记 02——注解和配置

146 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

注解 @SpringBootApplication

@SpringBootApplication 注解实际上封装了以下三个注解:

  • @SpringBootConfiguration 配置类注解
  • @EnableAutoConfiguration 启用自动配置注解
  • @ComponentScan 组件扫描注解

关于注解 @ServletComponentScan 和 @ComponentScan:

参考文章:blog.csdn.net/m0_37739193…

@ComponentScan(“{package name}”) 自动扫描包名下所有使用 @Service、@Component、@Repository 和 @Controller 的类,并注册为 @Bean 。

VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。

DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。

DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。

PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。

Spring MVC 视图解析器相关配置

  1. 新增 JSP 和 JSTL 的 Maven 依赖配置 - pom.xml

    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <scope>provided</scope>
    </dependency>
    
  2. 定义视图前后缀配置 - application.properties

    server.port =8090
    spring.mvc.view.prefix=/WEB-INF/jsp/
    spring.mvc.view.suffix=.jsp
    
  3. 新建控制器 IndexController.java

    这里定义了一个映射为 /index 的路径,然后方法返回了“index”,这样它就与之前配置的前缀和后缀结合起来寻找对应的 jsp 文件,为此还需要开发一个对应的 index.jsp 文件。

    package com.springboot.chapter2.main;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class IndexController {
      @RequestMapping("/index")
      public String index() {
        return "index";
      }
    }
    

自定义注解 @interface 和 @AliasFor 使用

@interface 参考文章:Java 注释 @interface 的用法

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                                 @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

  @AliasFor(annotation = EnableAutoConfiguration.class)
  Class<?>[] exclude() default {};

  @AliasFor(annotation = EnableAutoConfiguration.class)
  String[] excludeName() default {};

  @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  String[] scanBasePackages() default {};

  @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

  @AliasFor(annotation = Configuration.class)
  boolean proxyBeanMethods() default true;

}

@AliasFor 注解的几种使用方式

1. 在同一个注解中显示使用,将注解中的多个属性互相设置别名

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

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

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

  //...
}

为什么要给 value 属性和 path 属相互相设置别名也是有原因的。在 Spring 中给 value 属性设置值是可以省略属性的,比如可以写成:

RequestMapping("/foo")

这样写比较简洁,但是这样可读性不高,并不知道 value 属性代表什么意思。如果给这个属相设置一个 path 别名的话我们就知道这个是在设置路径。

但是要注意一点,@AliasFor 标签有一些使用限制:

  • 互为别名的属性属性值类型,默认值,都是相同的;
  • 互为别名的注解必须成对出现,比如 value 属性添加了 @AliasFor("path"),那么 path 属性就必须添加 @AliasFor("value");
  • 互为别名的属性必须定义默认值。

如果违反了别名的定义,在使用过程中就会报错。

2. 给元注解中的属性设定别名

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                                 @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

  @AliasFor(annotation = EnableAutoConfiguration.class)
  Class<?>[] exclude() default {};

  @AliasFor(annotation = EnableAutoConfiguration.class)
  String[] excludeName() default {};

  @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  String[] scanBasePackages() default {};
  //...
}

@SpringBootApplication 这个注解是由其他几个注解组合而成的。下面的代码就是在给 @ComponentScan 注解的 basePackages 属性设置别名 scanBasePackages。如果不设置 attribute 属性的话就是在给元注解的同名属性设置别名。

@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};

这种使用方式的好处是可以将几个注解的功能组合成一个新的注解。

@AliasFor 的实现代码

@AliasFor 的具体实现在 AnnotationUtils.findAnnotation 中。

依赖注入和 @Autowired

依赖注入(Dependency Injection, DI)即为定义 Bean 之间的依赖。

@Autowired 可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired 的使用来消除 set, get 方法。在使用 @Autowired 之前,我们这样对一个 Bean 配置属性:

<property name="属性名" value=" 属性值"/>

通过这种方式来,配置比较繁琐,而且代码比较多。在 Spring 2.5 引入了 @Autowired 注释。下面用案例来具体说明:

UserRepository.java

package com.proc.bean.repository;

public interface UserRepository {

  void save();
}

这里定义了一个 UserRepository 接口,其中定义了一个 save() 方法。

UserRepositoryImps.java

package com.proc.bean.repository;

import org.springframework.stereotype.Repository;

@Repository("userRepository")
public class UserRepositoryImps implements UserRepository{

  @Override
  public void save() {
    System.out.println("UserRepositoryImps save");
  }
}

定义一个 UserRepository 接口的实现类,并实现 save() 方法,在这里指定了该 Bean 在 IoC 中标识符名称为 userRepository。

UserService.java

package com.proc.bean.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.proc.bean.repository.UserRepository;

@Service
public class UserService {

  @Autowired
  private UserRepository userRepository;

  public void save() {
    userRepository.save();
  }
}

这里需要一个 UserRepository 类型的属性,通过 @Autowired 自动装配方式,从 IoC 容器中去查找到,并返回给该属性。

applicationContext.xml

<context:component-scan base-package="com.proc.bean" />

测试代码:

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
 
UserService userService = (UserService) ctx.getBean("userService");
userService.save();

输出结果:UserRepositoryImps save

 

在使用 @Autowired 时,首先在容器中查询对应类型的 Bean

  • 如果查询结果刚好为一个,就将该 Bean 装配给 @Autowired 指定的数据
  • 如果查询的结果不止一个,那么 @Autowired 会根据名称来查找
  • 如果查询的结果为空,那么会抛出异常。解决方法时,使用 required=false

举例说明,在上面例子中,我们再定一个类来实现 UserRepository.java 接口

package com.proc.bean.repository;

import org.springframework.stereotype.Repository;

@Repository
public class UserJdbcImps implements UserRepository {

  @Override
  public void save() {
    System.out.println("UserJdbcImps save");
  }
}

这时在启动容器后,在容器中有两个 UserRepository 类型的实例,一个名称为 userRepository,另一个为 userJdbcImps。

输出结果:UserRepositoryImps save。这里由于查询到有两个该类型的实例,那么采用名称匹配方式,在容器中查找名称为 userRepository 的实例,并自动装配给该参数。

如果这里想要装载 userJdbcImps 的实例,除了将字段 userRepository 名称改成 userJdbcImps 外,还可以使用 @Qualifier 标记,来指定需要装配 Bean 的名称,代码这样写:

@Autowired
@Qualifier("userJdbcImps")
private UserRepository userRepository;

输出结果:UserJdbcImps save

也可以在 UserJdbcImps.java 实现类中添加 @Primary 注解,表示当发现有多个同样类型的 Bean 时,优先使用此实现类进行注入。