SpringBoot编程思想阅读笔记
拓扑知识
版本号管理
从未思考过的版本号管理,例如3.0.3-RELEASE, 其中 第一位 为 主版本号,有控制大版本的更新,次一位代表次版本号,可小范围的引入新特性和相关API,第三位则用于问题修正或安全补丁等.
- RC 候选版本,正式版的候选
- GA 发行稳定版
- R,RELEASE代表正式版 等价于GA
- SNAPSHOT快照版本,可以稳定使用,且仍需继续改进版本
- Alpha 内部测试版
- Beta 外部测试版
- DEMO 演示版本
- Build 修正版
- Delux 豪华版
- Free 免费版
- Full 完全版
- Final 正式版
- Pro 专业版
- Plus 加强版
- SR 修正版
- Trial 试用版
JSR
Java Specification Requests的缩写,意思是Java 规范提案。
Validated 和 Valid 区别
Validated 是Spring Validation验证框架对验证机制提供的注解(Spring JSR303规范)
Valid 是Javax提供的(JSR303规范)
两者在Controller入参的级别验证时没什么区别,只在分组,使用范围,嵌套验证上有着不同
-
分组
- @Validated 提供了分组功能,可以在入参时根据不同的分组采用不同的验证机制
- @Valid 不具备分组功能
-
使用范围
- @Validated 可以用在类型,方法,方法参数上.但是不能用于成员属性上
- @Validated 可以用于方法,构造函数,方法参数和成员属性上
-
嵌套验证
public class Student{ @NotEmpty private String name; @Size(min = 6, max = 25) private Integer age; } public class Teacher{ @NotNull private List<Student> students; }
如上例,如若入参时使用@Validated,则只验证students上的条件,不能验证students里具体每个对象的条件,而@Valid可以用在成员属性上,即
@Valid @NotNull private List<Student> students;
这样不仅会验证students,而且会验证students里的每个对象的条件,此称之嵌套验证.
Java启动参数-D
-D 是用来在启动Java程序时设置的系统属性值,如果该值是一个字符串且包含空格,那就需要包在一对双引号中.设置的值,在程序中可以通过System.getProperties()来获取
java -Dusername=keben -DnickName=kobe Main
public class Main{
public static void main(String[] args){
String name = System.getProperty("username");
String nickName = System.getProperty("nickName");
System.out.println(name);
System.out.println(nickName);
}
}
终端格式化json
json_pp
例如: cat data.json | json_pp
SpringBoot启动方式
JarLauncher
是SpringBoot的启动器,当SpringBoot项目打成Jar包后,通过jar -jar执行
实际就是通过META-INF/MANIFEST.MF中的Main-Class配置的JarLauncher来执行的,
JarLauncher内会调用Start-Class里的main方法达到启动,而在Idea中我们可以直接通过Start-Class上的main函数启动.JarLauncher在执行时会把BOOT-INF/lib的JAR作为项目启动类的类库依赖.
public class JarLauncher extends ExecutableArchiveLauncher{
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
public JarLauncher(){}
protected JarLauncher(Archive archive){
super(archive);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry){
if(entry.isDirectory()){
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
public static void main(String[] args) throws Exception{
new JarLauncher().launch(args);
}
}
WarLauncher
同样也是SpringBoot的启动器,是Springboot项目打成war包的执行器.
Jar和War的区别
Jar内嵌tomcat,可直接通过java -jar运行,而war同时可以放在外部web容器中.
Jar中不能使用Jsp
War包必须有web.xml
固化Maven依赖
项目中使用spring-boot-starter-parent或spring-boot-dependencies来进行spring-boot各个包的版本管理.降低SpringBoot应用依赖管理的成本
在项目的pom文件中继承spring-boot-starter-parent.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
查看spring-boot-starter-parent可知它是继承了
spring-boot-dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
而spring-boot-dependencies中配置了一系列的依赖管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
...
</dependencies>
</dependencyManagement>
同时内部也配置了一些插件的管理
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.4.RELEASE</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-maven-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec-maven-plugin.version}</version>
</plugin>
...
</plugins>
</pluginManagement>
根据官方介绍,可以通过
- 继承spring-boot-starter-parent
- 在dependencyManagement中配置spring-boot-dependencies
WebServerApplicationContext
WebServerApplicationContext是SpringBoot2.0新引入的一种ApplicationContext的实现,用于创建和管理嵌入式Web容器的生命周期.服务器启动后可以通过其中的getWebServer来获取当前嵌入式容器
WebServer getWebServer();
String getServerNamespace();
ApplicationRunner
在开发中可能会有这样的情景。需要在容器启动的时候执行一些内容。比如读取配置文件,数据库连接之类的。SpringBoot给我们提供了两个接口来帮助我们实现这种需求。
这两个接口分别为CommandLineRunner和ApplicationRunner。他们的执行时机为容器启动完成的时候。
这两个接口中有一个run方法,我们只需要实现这个方法即可。这两个接口的不同之处在于:ApplicationRunner中run方法的参数为ApplicationArguments,而CommandLineRunner接口中run方法的参数为String数组.
WebServerInitializedEvent
WebServerInitializedEvent—>Web服务器已初始化事件.当Web服务器初始化完毕后会通知此事件.
在代码中通过@EventListener来监听事件.
@EventListener(WebServerInitializedEvent.class)
public void handler(WebServerInitializedEvent event){
log.info("[初始化] Web容器已初始化完毕")
log.info("[初始化] 当前Web容器是: {}",event.getWebServer().getClass().getName())
}
三种嵌入式容器
容器 | Maven依赖 | WebServer实现类 |
---|---|---|
Tomcat | spring-boot-starter-tomcat | TomcatWebServer |
Jetty | Spring-boot-starter-jetty | JettyWebServer |
Undertow | Spring-boot-starter-undertow | UndertowWebServer |
@EventListener
@EventListener事件监听器是Spring框架4.2版中引入的新特性,用于监听继承了ApplicationEvent的事件.
@EventListener(WebServerInitializedEvent.class)
public void onReady(WebServerInitializedEvent event) {
log.info("[Web容器初始化完毕,name:{}]",event.getWebServer().getClass().getSimpleName());
}
@Async
@EventListener(WebServerInitializedEvent.class)
public void onReady(WebServerInitializedEvent event) {
log.info("[Web容器初始化完毕,name:{}]",event.getWebServer().getClass().getSimpleName());
}
同时此注解还可配合@Async进行异步事件处理.
@AliasFor
别名注解,用在注解的属性上,相当于在把当前的属性设置成其派生的注解的某属性上.并且这个某属性指向与当前注解的属性.
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
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 = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor("testValues")
String[] testNames() default {};
@AliasFor("testNames")
String[] testValues() default {};
}
如上述代码所示,其中 exclude() 是 @EnableAutoConfiguration注解中exclude()的别名,
scanBasePackages() 是 @ComponentScan注解中basePackages()的别名.如若没有设置@AliasFor中的annotation属性,例如 testNames()和testValues(),它们两个则为互相的别名.
@Import
此注解是将资源导入到当前容器中,类似于@Component等.
public class Student{}
public class Config{
@Bean
public Student student(){
return new Student();
}
}
/** 导入类 **/
@Import(Student.class)
@SpringBootApplication
public class ApplicationTest{
public static void main(String[] args){
ConfigurableApplicationContext context = SpringApplication.run(ApplicationTest.class,args);
Student student = context.getBean(Students.class);
}
}
/** 导入资源 **/
@Import(Config.class)
@SpringBootApplication
public class ApplicationTest{
public static void main(String[] args){
ConfigurableApplicationContext context = SpringApplication.run(ApplicationTest.class,args);
Student student = context.getBean(Students.class);
}
}
用此注解导入的类会加上@Component注解从而被spring容器管理.
@Component和@Configuratin中的@Bean
@Component中@Bean的行为与正常的Java对象语义相同,不存在CGLIB处理,而@Configuratin中执行了CGLIB提升,同时若@Bean在普通类中被声明后,当该声明类注册到Spring应用上下文中,其Bean对象的行为与在@Component下的行为一致的.
官方称前者中@Bean的声明方式为"轻量模式"(Lite),相反,在@Configuration下声明的@Bean则属于"完全模式"(Full),后者会执行CGLIB提升操作.@Component中注册的,每次去获取Bean都是跟普通一样去new一个新的,而@Configuration中的则走了CGLIB代理,每次都是从容器中获取,获取到的都是同一个.
自动装配
场景: 在项目中需要加载第三方的配置类.
已知: SpringBoot默认会扫描启动类同包以及子包下的注解.
有三种解决方案:
- 在@SpringBootApplication注解中的scanBasePackages参数里设置 需要加载的配置类的路径
@SpringBootApplication(scanBasePackages = "c.test.thirdparty.config")
- 通过@Import注解把配置类导入
@Import(UserConfigure.class)
- 在spring.factories中配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
c.test.thirdparty.config.UserConfigure
推荐使用第三种方式,用这种的好处是 如果我们想开发一个包给别人使用,那么我们只要在包中的spring.factories里配置@Configuration类,这样别人只需引入此包,包中的类就会被自动装配到Spring容器中.例:
// thirdparty.jar 包
@Slf4j
@Configuration
public class ThirdPartyConfigure {
@Bean
public UserService userService() {
log.info("[第三方配置] 初始化第三方用户服务...");
return new UserService();
}
@Bean
public GoodsService goodsService() {
log.info("[第三方配置] 初始化第三方商品服务...");
return new GoodsService();
}
}
// thirdparty.jar 中的 META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
c.test.thirdparty.config.UserConfigure
// 别人的项目
// pom.xml
<dependency>
<groupId>c.test</groupId>
<artifactId>thirdparty</artifactId>
<version>0.0.1-RELEASE</version>
</dependency>
// Demo 代码中直接使用即可
@Autowired
private GoodsService goodsService;
外部化配置
外部化即在配置文件(yml,properties)中配置相关属性,并且能在SpringBoot场景中提供三种用途:
- Bean的@Value注入
- Spring Environment读取
- @ConfigurationProperties绑定到对象
@PostConstruct 和 @PreDestory
JSR-250规范中增加了两个注解@PostConstruct和@PreDestory,同样Spring也支持.
@PostConstruct注解的函数在Bean初始化后执行,一般用来进行初始化配置的操作,执行顺序: 构造器 -> @Autowired -> @PostConstruct
@PreDestory注解 在当前Bean销毁之前进行的操作,这两个注解类似于Xml配置中的init-method和destory-method
@Repeatable
Java8新增了@Repeatable注解,此注解可重复加在同一属性上,例如角色管理:
一个人可能即使前台的普通用户,还是后台的管理员.这时只能定义一个角色显然不够了.
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(value = Roles.class)
public @interface Role {
String value() default "普通用户";
}
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
Role[] value();
}
public class Test {
public static void main1(String[] args) throws NoSuchMethodException {
Roles annotation = Test.class.getMethod("Test",null).getAnnotation(Roles.class);
for (Role role : annotation.value()) {
System.out.println(role.value());
}
}
public static void main(String[] args) throws NoSuchMethodException {
Role[] annotation = Test.class.getMethod("Test",null).getAnnotationsByType(Role.class);
for (Role role : annotation) {
System.out.println(role.value());
}
}
@Role
@Role("后台管理员")
public void Test(){
System.out.println("hello world");
}
}
@Inherited
当一个注解上标注有@Inherited,那么这个注解应用于一个Class时,这个注解也将应用于该Class的子类上.只适用于extends关系,不适用于implement
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Role {
String value() default "普通用户";
}
@Role("超级VIP")
public class People {
}
public class Child extends People {
}
public static void main(String[] args) {
System.out.println(People.class.getAnnotation(Role.class).value());
System.out.println(Child.class.getAnnotation(Role.class).value());
}
元注解(Meta-Annotation)
所谓元注解是指一个能声明在其他注解上的注解.如果一个注解标注在其他注解上,那么这个注解就是元注解.例如 @Documented即是元注解.
Metadata
Metadata元数据,即定义数据的数据,主要有以下相关接口和类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9N9GU62-1575510027984)(media/15736938734742/15748607210522.jpg)]
其在Spring的Condition接口中有用到.
ClassMetadata接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SDULj5ne-1575510027985)(media/15736938734742/15748608153524.jpg)]
基本上都是Class相关的,所以ClassMetadata实质上就是对Class的一种抽象和适配.
AnnotatedTypeMetadata
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xeJu2IS0-1575510027986)(media/15736938734742/15748609212362.jpg)]
AnnotationMetadata
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zS9zuS1U-1575510027986)(media/15736938734742/15748609597550.jpg)]
基本上都是对Class或Annotation注解中相关信息的一些获取和判断.
@Role("VIP")
public class AnnoTest {
public static void main(String[] args) {
//通过AnnotationMetadata获取类的元数据信息
AnnotationMetadata introspect = AnnotationMetadata.introspect(AnnoTest.class);
// 获取类上的注解
System.out.println(introspect.getAnnotationTypes());
}
}
Spring模式注解
Spring模式注解即@Component"派生"注解.由于Java语言规范的规定,Annotation之间不允许继承,没有类派生子类的能力.因此Spring采用元标注的方式实现注解之间的"派生".而@Component作为Spring容器托管的通用模式组件,任何被@Component标注的类均为组件扫描的候选对象.类似的,凡是被@Component元标注的注解,如@Controller,当任何类标注它时,也被视作组件扫描的候选对象.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}
@Component的派生性
@Component的派生性的奥秘来自于ClassPathBeanDefinitionScanner,当doScan(String …basePackages)方法被ComponentScanBeanDefinitionParser#parse(Element,ParserContext)==(此类是用于解析 <context:component-scan base-package>相关标签的)==方法调用后,它将迭代执行findCandidateComponents(String basePackage),每次执行都会生成候选BeanDefinition(类似于xml时代中的标签的内容)集合.
如若想实现Dubbo中@Service那样,在不是@Component派生注解的同时,又会被加载到Spring容器,可自主继承ClassPathScanningCandidateComponentProvider类,并重写
registerDefaultFilters函数.
// Dubbo的@Service
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface Service {
Class<?> interfaceClass() default void.class;
String interfaceName() default "";
String version() default "";
...
}
// 自定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Component
public @interface Custom {
String value();
}
/**
* 实现使用自定义注解的类载入到spring容器
* 简单尝试 Dubbo中@Service的做法
*/
public class CustomScanner extends ClassPathScanningCandidateComponentProvider {
@Override
protected void registerDefaultFilters() {
super.registerDefaultFilters();
// 把当前注解加入到候选过滤队列中
this.addIncludeFilter(new AnnotationTypeFilter(Custom.class));
}
}
多层次@Component派生性原理
在Spring Framework3.0之前@Component只支持单继承,在3.0时开始支持两层的继承关系,只有在4.0以后才算是真正支持多继承.多继承的例如:
@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 = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
}
如上述所示三层继承关系.原理请查看AnnotationMetadataReadingVisitor中的visitAnnotation()函数
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (!visible) {
return null;
}
String className = Type.getType(desc).getClassName();
if (AnnotationUtils.isInJavaLangAnnotationPackage(className)) {
return null;
}
this.annotationSet.add(className);
return new AnnotationAttributesReadingVisitor(
className, this.attributesMap, this.metaAnnotationMap, this.classLoader);
}
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
private final MultiValueMap<String, AnnotationAttributes> attributesMap;
private final Map<String, Set<String>> metaAnnotationMap;
public AnnotationAttributesReadingVisitor(String annotationType,
MultiValueMap<String, AnnotationAttributes> attributesMap, Map<String, Set<String>> metaAnnotationMap,
@Nullable ClassLoader classLoader) {
super(annotationType, new AnnotationAttributes(annotationType, classLoader), classLoader);
this.attributesMap = attributesMap;
this.metaAnnotationMap = metaAnnotationMap;
}
@Override
public void visitEnd() {
super.visitEnd();
Class<? extends Annotation> annotationClass = this.attributes.annotationType();
if (annotationClass != null) {
List<AnnotationAttributes> attributeList = this.attributesMap.get(this.annotationType);
if (attributeList == null) {
this.attributesMap.add(this.annotationType, this.attributes);
}
else {
attributeList.add(0, this.attributes);
}
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationClass.getName())) {
try {
Annotation[] metaAnnotations = annotationClass.getAnnotations();
if (!ObjectUtils.isEmpty(metaAnnotations)) {
Set<Annotation> visited = new LinkedHashSet<>();
for (Annotation metaAnnotation : metaAnnotations) {
recursivelyCollectMetaAnnotations(visited, metaAnnotation);
}
if (!visited.isEmpty()) {
Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());
for (Annotation ann : visited) {
metaAnnotationTypeNames.add(ann.annotationType().getName());
}
this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
}
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect meta-annotations on " + annotationClass + ": " + ex);
}
}
}
}
}
// 重点
// 递归加载所有注解上的注解进行检测
private void recursivelyCollectMetaAnnotations(Set<Annotation> visited, Annotation annotation) {
Class<? extends Annotation> annotationType = annotation.annotationType();
String annotationName = annotationType.getName();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) && visited.add(annotation)) {
try {
if (Modifier.isPublic(annotationType.getModifiers())) {
this.attributesMap.add(annotationName,
AnnotationUtils.getAnnotationAttributes(annotation, false, true));
}
for (Annotation metaMetaAnnotation : annotationType.getAnnotations()) {
recursivelyCollectMetaAnnotations(visited, metaMetaAnnotation);
}
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect meta-annotations on " + annotation + ": " + ex);
}
}
}
}
}
Spring注解驱动设计模式
从Spring Framework 3.1开始支持"@Enable模块驱动", 所谓"模块"是指具备相同领域的功能组件集合,组合所形成的一个独立的单元,例如WebMVC模块,AspectJ代理模块,Caching模块,Async模块等.
在Framework 3.1中,框架实现者有意识地形成了一种新"设计模式",这种设计模式有别于传统的面向对象GoF23设计模式,称之为"@Enable 模块驱动".
@Enable模块驱动在后续Spring Framework,SpringBoot和SpringCloud中一以贯之,这种模块化的Annotation均以@Enable作为前缀.
引入"@Enable 模块驱动"的意义在于能够简化装配步骤,实现"按需装配",同时屏蔽组件集合装配的细节,然而该模式必须手动触发,也就是说是实现Annotation必须标注在某个配置Bean中,同时实现该模式的成本相对较高,尤其是在理解其中的原理和加载机制以及单元测试方面.
基于"注解驱动"实现@Enable模块
既然是模仿实现,肯定是少不了围观下Spring原有的注解,例如@EnableScheduling
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
可以看出,它的内部导入了SchedulingConfiguration.class
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
而SchedulingConfiguration是一个配置类,内部创建了一个ScheduledAnnotationBeanPostProcessor.这种实现方式提供了一个样版.根据上面的经验,我们自己实现"注解驱动实现"的"@Enable模块驱动".
/**
* 开启单点登录模块注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SsoConfiguration.class)
@Documented
public @interface EnableSso {
}
/**
* sso配置类
*/
@Configuration
public class SsoConfiguration {
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
}
@EnableSso
@SpringBootApplication
public class TestSpringApplication {
public static void main(String[] args) {
// 构建Annotation配置驱动Spring上下文
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//注册当前启动类到Spring上下文
applicationContext.register(TestSpringApplication.class);
//启动上下文
applicationContext.refresh();
// 获取Bean对象
LoginInterceptor userInterceptor = applicationContext.getBean("loginInterceptor", LoginInterceptor.class);
System.out.println(userInterceptor);
applicationContext.close();
}
}
基于 “接口编程” 实现@Enable模块
接口编程需要实现ImportSelector或ImportBeanDefinitionRegistrar接口:
-
ImportSelector
ImportSelector接口相对简单,使用Spring注解元信息抽象AnnotationMetadata作为方法参数,该参数的内容为导入ImportSelector实现的@Configuration类元数据,进而动态地选择一个或多个其他@Configuration类进行导入. -
ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar相对于ImportSelector而言,其编程复杂度更高,除注解元信息AnnotationMetadata作为入参外,接口将Bean定义(BeanDefinition)的注册交给开发人员决定.
ImportSelector
来看一下通过实现ImportSelector接口的使用方式,例如@EnableCaching.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
通过@Import载入CachingConfigurationSelector.class,再看一下CachingConfigurationSelector.class.
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
private static final String PROXY_JCACHE_CONFIGURATION_CLASS =
"org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJCachingConfiguration";
private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJJCacheConfiguration";
private static final boolean jsr107Present;
private static final boolean jcacheImplPresent;
static {
ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
}
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
private String[] getProxyImports() {
List<String> result = new ArrayList<>(3);
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return StringUtils.toStringArray(result);
}
private String[] getAspectJImports() {
List<String> result = new ArrayList<>(2);
result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
if (jsr107Present && jcacheImplPresent) {
result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
}
return StringUtils.toStringArray(result);
}
}
可以看出此类中实际上只是根据AdviceMode选择不同的切入方式的配置,PROXY动态代理,和ASPECTJ切入.
主要函数还是在其父类中AdviceModeImportSelector实现的selectImports(AnnotationMetadata importingClassMetadata)
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";
protected String getAdviceModeAttributeName() {
return DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME;
}
@Override
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
// importingClassMetadata 是标注着@EnableCaching注解的类的AnnotationMetadata
// 获取CachingConfigurationSelector类上接口泛型EnableCaching
Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");
// 获取类上此注解的所有属性和值
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
if (attributes == null) {
throw new IllegalArgumentException(String.format(
"@%s is not present on importing class '%s' as expected",
annType.getSimpleName(), importingClassMetadata.getClassName()));
}
// 根据@EnableCaching中参数名mode 获取其枚举值
AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
// 根据枚举选择子类中实现的相应配置
String[] imports = selectImports(adviceMode);
if (imports == null) {
throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
}
return imports;
}
@Nullable
protected abstract String[] selectImports(AdviceMode adviceMode);
}
如若实际中使用可同样继承AdviceModeImportSelector或实现ImportSelector接口.
ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar,可以通过此类动态注册Bean到Spring容器,用法和ImportSelector类似,唯一不同点是void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry),此函数允许我们直接通过BeanDefinitionRegistry对象注册Bean.
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
如其实现类
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
// 获取注解属性
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
// 根据不同的参数,设置不同的代理
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
// 强制使用cglib代理
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
// 强制公开代理(暴露代理,即把当前的代理对象存入到ThreadLocal中再次使用时再次从ThreadLocal中获取即可)
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
配合注解使用,以达到@Enable模块"接口编程"的效果.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
什么是exposeProxy(暴露代理)?
如在一个类中:
public class UserServiceImpl implements UserService{
@Transactional(propagation = Propagation.REQUIRED)
public void update() {
this.delete();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void delete() {
}
}
此时调用update触发执行事务切面,内部调用的this.delete()将不会执行delete()事务切面,即不会执行事务增强,因此delete()的事务定义"@Transactional(propagation = Propagation.REQUIRES_NEW) "将不会实施,即结果是delete()的事务和update()的事务定义是一样的.为什么呢?
因为update()中调用delete()属于内部调用,没有通过代理,所以不会有事务产生.如果想产生事务,需要走代理,即需要@EnableAspectJAutoProxy(exposeProxy = true),将代理暴露出来,使用AopContext.currentProxy()获取当前代理,将this.delete()改为((UserService)AopContext.currentProxy()).delete();
Spring中Propagation的7种事务配置
Spring中七种Propagation类的事务属性详解:
-
REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
-
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
-
MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
-
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
-
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
-
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
-
NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。