SpringBoot编程思想阅读笔记

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实现类
Tomcatspring-boot-starter-tomcatTomcatWebServer
JettySpring-boot-starter-jettyJettyWebServer
UndertowSpring-boot-starter-undertowUndertowWebServer

@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:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值