回顾Spring声明
在传统Spring项目中,通过 .xml 文件进行容器内容物的管理,包括声明bean、声明配置、开启功能、bean的自动加载等。配置文件通常声明为spring.xml(或application.xml),在web容器的配置起点web.xml中,声明配置文件的路径,完成spring服务的配置路径起始声明。以下是一个小的回顾示例。
<!-- 开启AOP(默认使用CGLIB创建代理)-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 声明Spring的bean扫描路径-->
<context:component-scan base-package="com.company.department.service, com.company.department.another.service">
<!--将Controller的注解排除掉,因还会有spring-mvc.xml配置声明controller -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--其他拒绝引入的包 -->
<context:exclude-filter type="regex" expression="com.company.exclude.department.*"/>
</context:component-scan>
<!-- 单独声明bean和构造函数 -->
<bean name="someBizService" class="com.company.relied.department.SomeBizService">
<constructor-arg name="someParam" value="specifiedParam" />
<constructor-arg name="userName" value="${property.user.name}" />
<constructor-arg name="timeout" value="1000" />
</bean>
<!-- 单独声明bean和setter -->
<bean name="authorizeEncodeService" class="com.company.relied.department.PropertiedService">
<property name="propName" ref="someBizService"/>
</bean>
<!-- 配置文件声明 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config.properties</value>
<value>classpath:profile.properties</value>
<value>classpath:META-INF/app.properties</value>
<value>classpath:jetty/boot.properties</value>
<value>classpath*:cache.properties</value>
<value>classpath*:lock.properties</value>
</list>
</property>
</bean>
<!-- 关联引入其他配置文件 -->
<import resource="spring-relied-config.xml"/>
Springboot的声明方式变更
在Springboot项目中,对以上的功能有逐项的替代。
声明bean扫描路径,及三方package的引入
Springboot的bean扫描路径可通过注解进行自定义。在常见项目中,通常声明为自己项目本身的package路径。
package org.example.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = {"org.example"})
public class DemoBootApplication {
public static void main(String[] args) {
SpringApplication.run(DemoBootApplication.class, args);
}
}
对引入三方package的场景,目前收集到的解法:
1. 显式声明依赖
在传统Spring项目中,通常会同时配置三方的package到扫描路径下,类似上文的com.company.department.another.service,将三方的@Component等注解声明的类直接引入。在Springboot中有类似的解法:即在启动类的@SpringBootApplication上,声明三方包的路径。
但是,由于Springboot可以通过@Configuration + spring.factories、动态注入bean等方式,让三方引用本jar文件时,自动将声明的bean注入到容器中,所以目前遇到的项目中,并未声明依赖的三方package,大概不符合Springboot的惯常使用习惯。
2. spring.factories(最常见)
当我们需要自定义通用的bean,并期望可以被引用方自动加载生效时(如部分AOP,拦截器等),可以按以下思路,编写spring.factories,完成定义。代码如下。
java类:
package org.example.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Slf4j
@Configuration
public class SomeAdvice{
public Object someMethod(MethodInvocation invocation) throws Throwable {
// do something.
return null;
}
}
spring.factories文件(位于resources/META-INF下)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.aspect.SomeAdvice
至此,SomeAdvice类即可在jar包引用时自动加载到springboot容器并生效了。此方式的特殊之处是,可以实现自动注入到引用本jar的其他项目组中。
spring-boot也使用此方式声明依赖以完成自动注入,以实现业务不需声明bean引入,引用jar文件即实现自动加载。spring-boot的spring.factories解析可见:springboot核心基础之spring.factories机制
原理:spring-core 包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。此外,它会遍历classLoader下所有jar包的spring.factories配置,并将所有文件中的key和value收集为同一个multiMap的归并,所以各文件取并集,不存在相互覆盖的风险。
3. Import注解(依托其他注解)
通过@Import注解,可以单独声明外部的某个类,springboot容器启动时会自动尝试加载对应的实例,如果对方有Spring标准注解,即可被加载。@Import注解需要声明在@Configuration类上或Springboot启动类上等可以被Spring代理到的类上面,效果类似声明到spring.factories的EnableAutoConfiguration配置。
此外,可以查看框架代码中的Configuration的实现以获得直观印象,如spring-boot-starter-data-redis中Redis的默认配置RedisAutoConfiguration。
4. Bean注解和BeanFactory(依托其他注解)
通过@Bean注解,可以单独声明外部的某个类,springboot容器启动时会自动尝试加载对应的实例,使用效果与Import类似,但是不得声明在需要依赖对应类的类中(比如AClass中需要加载BClass的bean,那么BClass的builder就不能在AClass中声明,否则会循环依赖)。当然从编码的美观程度来说,声明@Bean的类也不应当和需要使用此bean的类编写在同一个java文件中。
具体编码方面,需要声明在@Configuration类上或Springboot启动类上等可以被Spring代理到的类中,自定义方法之上。在方法中返回指定的bean,此时方法名就是生成的bean的名字。
相较于@Import,该注解的长处是可以通过方法入参等方式注入内部属性,通过setter完成构造,类似传统Spring声明中的<property>。
5. 动态注入(可按package批量引入,更灵活)
Springboot提供了动态注入机制(关键类ImportBeanDefinitionRegistrar),开发人员可以实现此接口来实现动态注入。有几种常见的使用方式:
1) 可以使用Spring默认的BeanScanner(ClassPathBeanDefinitionScanner)并自定义package,来指定加载目标package的类。此时的效果与提供spring.factories文件的效果类似,但能提供按package批量注入。
2) 可以自定义Scanner继承默认Scanner来实现自定义扫描,进行一些include/exclude定义,提供较强定制化的bean扫描与加载效果。
3) 可以结合自定义注解,实现类似Spring官方注解的效果,注解后即可自动被Spring代理,并加载到容器中。