简介
Spring 是一款开源的 J2EE 框架,它有许多项目,为 Java 应用开发提供了一整套的工具,其中最核心的就是 Spring Framework 和 Spring Boot 项目。
文本是一个系列文章的第一篇,下面就这两个项目的核心内容做一些速查整理,同时辅以生产源码,便于理解。
相关文章
- Spring 速查指南(一)- 依赖注入
- Spring 速查指南(二)- 环境、资源、事件、定时任务
- Spring 速查指南(三)- SpEL & 缓存
- Spring 速查指南(四)- Spring AOP
- Spring 速查指南(五)- Spring Web
依赖注入
注册 Bean
XML 文件配置的方式注册 Bean 是最初的方式,但在现代的应用程序中已不推荐使用,建议使用注解或配置类的方式。
注解方式
Spring 提供了 @Component、@Service、@Controller、@Repository、@Configuration 等注解来标记类,然后通过 @ComponentScan 来配置要扫描的包,自动扫描并导入相应的 Bean。
@Service
public class UserService {
}
这几个注解都有一个参数,用于指定 Bean 的名字,如果不提供,则默认是类名(首字母小写)。
这几个注解都可以注册 Bean,但场景不同。@Component 一般表示通用的组件,@Service 一般表示服务层,@Controller 一般表示控制层,@Repository 一般表示数据层,@Configuration 一般表示配置。其中 @Configuration 有点特殊,比其他注解多了 proxyBeanMethods
参数(默认 true),一般用于配置注册其他 Bean(下面会讲)。
而要上述注解标记的类能被扫描到,需要在运行的主类上添加 @ComponentScan 注解。
@SpringBootApplication
// 指定要扫描的组件的包的路径,** 表示包含其下的子包(子包的子包等)
@ComponentScan("com.example.**")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
配置类方式
使用配置类(@Configuration 注解标记的类)和 @Bean 注解标记的方法可以编程方式实例化并注册类。这种方式可以实例化第三方的类,并执行一些初始化代码。
@Configuration
public class ServerConfig {
/** 注册 Bean,方法名即是 Bean 的名字 */
@Bean
public AuthenticationManager authenticationManager() {
AuthenticationManager authenticationManager = new AuthenticationManager();
// 执行一些操作
return authenticationManager;
}
}
这种方式可以做一些逻辑判断,例如返回 null 则不注册,或者添加 @ConditionalOnXXX 注解,来根据条件注册。
常用的 @ConditionalOnXXX 注解有以下几种:
- @ConditionalOnClass:存在指定类时,才算符合条件
- @ConditionalOnMissingClass:不存在指定类时,才算符合条件
- @ConditionalOnBean:存在指定名称的 Bean 时,才算符合条件
- @ConditionalOnMissingBean:不存在指定名称的 Bean 时,才算符合条件
- @ConditionalOnProperty:检查配置是否满足特定的值
@Configuration 有个特殊的参数 proxyBeanMethods(默认 true)。为 true 时开启代理模式,这个类会被代理类所替换,在类中的方法中调用 @Bean 注解的方法时,不会重复调用,也就是一个 @Bean 的方法可以获取另一个 @Bean 方法的返回值,不会重复生成 Bean。为 false 则不会使用代理模式,类似于普通的 @Component。对于一般的配置类(主要用于注册 Bean,注册的 Bean 又是其他注册方法的参数),建议开启,如果 Bean 之间没有互相调用,可以关闭,以减少开销。
配置属性类注入
这种方式主要是注入配置属性,例如添加自定义配置的时候,自动注入配置属性类。
在配置属性类中用 @ConfigurationProperties 注解标记,这个类是简单 Java 对象,主要用于映射配置。
@ConfigurationProperties(prefix = CorsProperties.PREFIX, ignoreInvalidFields = true)
public class CorsProperties {
public static final String PREFIX = "cors";
/** 是否启用 */
private boolean enabled = false;
/** 缓存时间 */
private long maxAge = 86400L;
// 省略 getter 和 setter
}
上面定义的配置属性类会从配置文件中关联如下的配置:
cors:
enabled: false
maxAge: 86400
在配置类中,使用 @EnableConfigurationProperties 注解引入并注册。
@Configuration
// 引入配置属性类
@EnableConfigurationProperties({CorsProperties.class})
public class WebConfig {
// 其他配置
}
Import 方式
@Import 注解可以动态的注册 Bean,@Import 注解可以搭配 @Configuration 注解或 @EnableXXX 注解使用,实现自动配置。
@Configuration
// 可导入多个类
@Import({ConfigA.class, ConfigB.class})
public class WebConfig {
// 其他配置
}
导入的类可以有以下几种方式:
- 普通类:普通类会直接注册 Bean。
- 实现了 ImportSelector 接口(及其子接口):ImportSelector 接口有个 selectImports 方法,返回类全限定名数组,然后会注册这些 Bean。其中一个子接口 DeferredImportSelector,实现了延迟注册,会在所有配置类注册完成之后再注册。
- 实现了 ImportBeanDefinitionRegistrar 接口:ImportBeanDefinitionRegistrar 接口有个 registerBeanDefinitions 方法,可在该方法中直接注册 Bean。
public class ConfigA implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 返回类的全限定名数组
return new String[]{"com.bean.User"};
}
}
Spring 中 @EnableCaching 就使用了 ImportSelector 方式。
Spring 中 @EnableAspectJAutoProxy 就使用了 ImportBeanDefinitionRegistrar 方式。
ConfigurableBeanFactory 接口
通过获取 ConfigurableBeanFactory 实例,可以调用它的 registerSingleton 来注册单例 Bean。Hutool 的 SpringUtil 工具类就是用了这种方式。
注入 Bean
注入 Bean 是指在业务类中,从 IoC 容器获取已注册的 Bean,而不需要自己实例化类。
注解方式
Spring 提供了 @Autowired 和 @Qualifier 两个常用的注解来实现 Bean 注入。
@RestController
public class UserController {
@Autowired
private UserService userService;
}
这两个注解可互为补充,它们的区别是:
- @Autowired 通过类型注入,查找特定的类型或实现接口的实例,但如果注入的接口有多个实现或类型有多个实例,则会报错。
- @Qualifier 通过 Bean 名称注入,可以通过参数指定,默认取变量名,查找名称不会重复,但可能类型不匹配。
它们可以结合起来一起使用,通过类型和名称确定 Bean。
J2EE 另外提供了一个 @Resource 注解,也是被 Spring 所支持的。@Resource 注解有多个参数配置,可根据名称或类型(或都匹配)注入。Spring 也支持 JSR 标准的几个注解(@Inject、@Named、@ManagedBean 等),和 Spring 的注解类似,但不推荐使用,这里可以看到它们的差异。
构造函数方式
注解的方式需要在属性上添加额外的注解,对于 Spring 环境之外不太友好,不利于非 Spring 环境下的使用。另一种方式是使用构造函数注入,在非 Spring 环境中可以直接通过构造函数传参创建,而在 Spring 环境中,Sping 会根据类型自动注入。这也是 Spring 官方更推荐的方式。
这种方式搭配 Lombok 的 @RequiredArgsConstructor 注解使用起来非常方便,对需要自动注入的属性声明为 private final
即可。
@Component
// 使用 Lombok 的注解自动生成构造函数
@RequiredArgsConstructor
public class PrepareDatasource implements IBaseCommand {
private final TplDatasourceService datasourceService;
private final DatasourceProcessor datasourceProcessor;
}
这种方式开发第三方库可以避免 Spring 的依赖。
实现 Aware 接口
Spring 提供了几个 Aware 接口,用于获取对应的实例,如 BeanFactoryAware、ApplicationContextAware 接口就可以分别用来获取 BeanFactory、ApplicationContext(官方推荐应优先使用 ApplicationContext)。另外继承 ApplicationObjectSupport 类(包括其他子类)也是一样的,它就是实现了 ApplicationContextAware 接口的抽象类。
它们都有一个 setXXX 方法,Spring 会在实例化它们之后,调用 setXXX 方法,把对应的实例注入,我们只需要在这个类中用一个属性接收即可。获取到 BeanFactory 或 ApplicationContext 之后,即可通过它们的 getBean 方法获取 Bean。
@Component
public class Component1 implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
public void test1() {
this.context.getBean("...")
}
}
这种方式一般主要用于获取动态的 Bean。
工具类方式
这种方式就是通过工具类的方式包装,获取 ApplicationContext 之后来获取 Bean,和上面的方法类似。主要的工具类有 Spring Web 的 WebApplicationContextUtils,Hutool 的 SpringUtil 等,都是静态方法,可以直接使用。另外 Spring Web 的 ContextLoader 也有个静态方法 getCurrentWebApplicationContext 可以获取 ApplicationContext。
这种方式也可以用于第三方库,兼容 Spring 环境或非 Spring 环境。
获取配置文件值
注入属性类
参考上面 配置属性类注入
定义配置属性类并注册后,获取 Bean 可直接获取属性。
@Value 注解获取
使用 @Value 注解可直接获取配置。
在属性上获取
@RestController
public class TestController {
// 获取配置 server.port
@Value("${server.port}")
private String port;
// ...
}
也可以在参数上获取
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
支持 SpEL 表达式(使用 #{}
)
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
Bean 注入冲突
如果使用 @Autowired 注解注入 Bean,存在多个实例时会产生错误。这时就可以使用 @Primary 注解标记主要的实例,可以用在任何注册 Bean 的地方。
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
这样获取的就是 firstMovieCatalog 实例了。
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
Bean 生命周期钩子
实现接口
如果想在 Bean 初始化之后或者销毁之前执行一些代码,可以实现 InitializingBean、DisposableBean 或 BeanPostProcessor 等接口,它们提供了一些方法来介入 Bean 的生命周期。
- InitializingBean 的 afterPropertiesSet 方法,在 Bean 初始化之后执行相关代码。
- DisposableBean 的 destroy 方法,在 Bean 销毁前执行相关代码。
- BeanPostProcessor 的 postProcessBeforeInitialization 方法,在 Bean 初始化之前执行相关代码。
- BeanPostProcessor 的 postProcessAfterInitialization 方法,在 Bean 初始化之后执行相关代码。
方法注解
另外也可以使用 JSR-250 标准提供的注解:@PostConstruct、@PreDestroy。这两个注解用在类的方法上,用于在对应的时机调用方法。使用这种方式的话就可以与 Spring 解绑,兼容非 Spring 环境了。
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// 实例化之后初始化,例如关联实例,引入资源等
}
@PreDestroy
public void clearMovieCache() {
// 销毁实例前清理,例如释放资源
}
}
@Bean 注解
@Bean 注解也提供了参数,用来指定初始化和销毁的方法。
public class BeanOne {
public void init() {
// 初始化逻辑
}
}
public class BeanTwo {
public void cleanup() {
// 销毁逻辑
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
注:销毁方法默认值是推断,如果类中含有公开的
close
或shutdown
方法,那么它们就会被注册成为销毁方法。如果不希望这样,需要手动指定 destroyMethod 或者换个方法名。
当然如果一个 Bean 结合了上述的多种方式(最好不要这么做),那么它的执行顺序是怎么样的呢?简单来说,@PostConstruct/@PreDestroy > 接口 > 自定义方法。
Aware 接口
Spring 提供了很多 Aware 接口,可以被理解为感知接口,这些接口提供了一个 setXXX 方法,用于在 Bean 初始化之后注入 Spring 的一些重要对象。
主要的一些 Aware 接口有:
- ApplicationContextAware:获取 ApplicationContext
- ApplicationEventPublisherAware:获取 ApplicationEventPublisher,用于发布事件
- BeanClassLoaderAware:获取 Bean 的类载入器
- BeanFactoryAware:获取 BeanFactory
- BeanNameAware:获取 Bean 名称
- LoadTimeWeaverAware:获取 LoadTimeWeaver,可实现类的动态加载
- MessageSourceAware:获取 MessageSource,可实现多语言国际化
- NotificationPublisherAware:获取 NotificationPublisher,可发送 JMX 通知
- ResourceLoaderAware:获取 ResourceLoader,可用来加载 resources 中的资源
- ServletConfigAware:在 Web 应用中获取 ServletConfig
- ServletContextAware:在 Web 应用中获取 ServletContext
(未完待续)
如果觉得有用,请多多支持,点赞收藏吧!