[笔记]SpringBoot实战(1)-Spring 4.x

系列笔记:

简单记录下《JavaEE开发的颠覆者SpringBoot实战》的学习心得。 先贴下读者学习时的一些代码记录:


本书基于 Spring 4.XSpring Boot 1.3.0 展开(最低要求Java1.6,推荐1.8),主打基于注解和 Java 配置的零配置(无xml配置),由于读者学习时是基于Spring Boot 2.1.1 进行的,故文中 Spring的源码是基于Spring 5.1的,Spring Boot 的源码是基于2.1.1的 。

Spring @Bean

Java配置是Spring 4.x、Spring Boot 推荐的配置方式,可以完全替代想,xml配置。

在 Spring 中是通过@Configuration和@Bean来实现的:

  • @Configuration 声明当前类时一个配置类,相当于一个Spring配置的xml文件
  • @Bean 注解在方法上,声明当前方法的返回值为一个Bean(Bean的名称可以是方法名)。

    在Spring容器中,只要容器中存在某个Bean,就可以在另外一个Bean的声明方法的参数中注入。@Bean 代表将当前方法返回的 POJO 装配到 IoC 容器中,而其属性 name 定义这个 Bean 的名称,如果没有配置它,则将当前方法名称作为 Bean 的名称保存到 Spring IoC 容器中 。

Spring 对 Bean的声明周期朝族偶支持两种方式:

  • Java 配置方式:使用@Bean的 initMethod 和 destoryMethod(相当于xml配置的init-method和destory-xml)。例如:

    @Bean(initMethos="init",destoryMethod="destory")

  • 注解方式:利用JSR-250的 @PostConstruct和@PreDestory(需要引入 jsr250-api jar包)。

分别代表在构造函数执行完之后进行和在Bean销毁之前执行。

条件注解@Conditional

@Confition 可根据满足某一特定条件创建一个特定的Bean(可以根据特定条件来控制Bean的创建行为)。在Spring Boot 中有大量应用。

相关特定条件类可通过实现 Condition 接口的 matches() 方法来定义判断条件,然后通过@Conditional(XXXCondition.calss) 来使用。

Profile

Profile 为在不同环境下使用不同配置提供了支持。
通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。在开发中使用@Profile注解类或者方法,达到在不同情况下选择实例化不同的Bean。
可通过设定jvm的spring.profiles.active 参数来设置配置环境。

使用时应该先设置活动的Profile,后注册Bean配置;类,不然会报Bean未定义的错误。

Spring 中简单使用示例:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();  
ctx.getEnvironment().setActiveProfiles("prod");  
ctx.register(XXXConfig.class);  
ctc.refresh();  

Spring AOP

AOP: 面向切面的编程,相对于OOP面向对象编程。
SpringAOP存在的目的是为了解耦。AOP可以让一组类共享相同的行为,在OOP中只能通过继承类(且为单继承)和实现接口,会使代码的耦合度增强,AOP弥补了OOP的不足。

Spring 支持 AspectJ 的注解式切面编程:

  • 1、使用@Aspect 声明是一个切面
  • 2、使用@After、@Before、@Around 定义建言(advice),可直接将拦截规则(切点)作为参数。
  • 3、其中@After、@Before、@Around 参数的兰姐规则称为切点(PointCut),为了使切点复用,可使用 @PointCut 专门定义拦截规则,然后在@After、@Before、@Around 的参数中调用。
  • 4、其中符合条件的每一个被拦截处为连接点(JoinPoint)

Spring 支持基于注解拦截和基于方法规则拦截两种方式,注解式拦截可以很好的控制要拦截的粒度和获得更丰富的信息,Spring本身在事务处理(@Transcational)和数据缓存(@Cacheable)等上面都是使用此种形式的拦截。

启用Spring AOP 需要引入 spring-aop、aspectjrt、aspectjweaver 包。
可通过 @Aspect 声明一个切面,使用 @Component 让该切面成为Spring容器管理的Bean。
通过 @PointCut 声明切点。
通过 @After、@Before、@Around 等声明一个建言,可以使用@PointCut定义的切点,如:如:@Pointcut("@annotation(com.xxx.aop.Action)");也可以直接使用拦截规则作为参数,如:@Before("execution(*com.xxx.aop.XXXService.*(..))")
通过 @EnableAspectJAutoProxy 注解开启Spring对AspectJ的支持。

后记

  • 如果被代理的目标对象实现了接口,那么Spring会默认使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。
  • 如果是被代理类的方法自调用,在自调用的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生 AOP,因为这样Spring就不能把你的代码织入到约定的流程中。
  • 需要代理的对象方法不能是private的,因为Spring不管使用的是JDK动态代理还是CGLIB动态代理,一个是针对接口实现的类,一个是通过子类实现。无论是接口还是父类,显然都不能出现 private 方法,否则子类或实现类都不能覆盖到。如果方法为private,那么在代理过程中,根本找不到这个方法,引起代理对象创建出现问题,也就可能会导致有的对象没有注入成功。

元注解

所谓元注解其实就是可以注解到别的注解上的注解,被注解的注解称之为组合注解。

Spring 中有很多组合注解,比如:@Configuration 就是一个组合 @Component 注解,表名这个类其实也是一个Bean。

Java注解之 @Target、@Retention、@Documented

先来看一个Spring中的一个常用注解示例:

package org.springframework.stereotype;  
import java.lang.annotation.Documented;  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 组合 @Component 注解
public @interface Controller {  
    /**
     * The value may indicate a suggestion for a logical component name,
     * to be turned into a Spring bean in case of an autodetected component.
     * @return the suggested component name, if any
     */
        // 覆盖 value 参数
    String value() default "";
}

@Target({ElementType.TYPE})

ElementType 这个枚举类型的常量提供了一个简单的分类:注释可能出现在Java程序中的语法位置(这些常量与元注释类型(@Target)一起指定写入注释的合法位置在何处)。

源码摘要:

package java.lang.annotation;  
// @since 1.5
public enum ElementType {  
    /** 类, 接口 (包括注释类型), 或 枚举 声明 Class, interface (including annotation type), or enum declaration */
    TYPE,
    /** 字段声明(包括枚举常量)Field declaration (includes enum constants) */
    FIELD,
    /** 方法声明 Method declaration */
    METHOD,
    /** 正式的参数声明 Formal parameter declaration */
    PARAMETER,
    /** 构造函数声明 Constructor declaration */
    CONSTRUCTOR,
    /** 局部变量声明 Local variable declaration */
    LOCAL_VARIABLE,
    /** 注释类型声明 Annotation type declaration */
    ANNOTATION_TYPE,
    /** 包声明 Package declaration */
    PACKAGE,
    /**
     * 类型参数声明 Type parameter declaration
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * 使用的类型 Use of a type
     * @since 1.8
     */
    TYPE_USE
}

@Retention({RetentionPolicy.Runtime})

RetentionPolicy 这个枚举类型的常量描述保留注释的各种策略,它们与元注释(@Retention)一起指定注释要保留多长时间。
源码摘要:

package java.lang.annotation;  
// @since 1.5
public enum RetentionPolicy {  
    /**
     * 注释只在源代码级别保留,编译时被忽略 (Annotations are to be discarded by the compiler)
     */
    SOURCE,
    /**
     * 注释将被编译器在类文件中记录,但在运行时不需要VM保留。这是默认的行为。
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,
    /**
     * 注释将被编译器记录在类文件中,在运行时被VM保留,因此可以反读。
     * (Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.)
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

@Documented

表示默认情况下,javadoc 和类似工具将记录带有类型的注释。 此类型应用于注释类型的声明,其注释会影响其客户端对带注释元素的使用。 如果使用 Documented 注释类型声明,则其注释将成为带注释元素的公共API的一部分。

Spring 单元测试

Spring 提供了一个 SpringJUnit4ClassRunner 类,他提供了 Spring TestContect Framework 的功能。通过 @ContextConfiguration 来配置 Application Context,通过 @ActiveProfiles 确定活动的profile。

import org.junit.runner.RunWith;  
import org.springframework.test.context.ActiveProfiles;  
import org.springframework.test.context.ContextConfiguration;  
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

// RunWith是指定使用的单元测试执行类(指定的类需继承org.junit.runners.BlockJUnit4ClassRunner,@since junit 4.4)
// 在junit环境下提供 Spring TestContect Framework 的功能,
@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = {XXXConfig.class}) // 用来加载配置 ApplicationContext,其中 classes 属性用来加载配置类。
@ActiveProfiles("prod") // 用来声明活动的 profile
// 这个用于指定在测试类执行之前,可以做的一些动作,TransactionalTestExecutionListener.class用于对事务进行管理
@TestExecutionListeners({ TransactionalTestExecutionListener.class })
/**
 * 不是必须的,这里是和@TestExecutionListeners中的TransactionalTestExecutionListener.class 
 * 配合使用,用于保证插入的数据库中的测试数据,在测试完后,事务回滚,将插入的数据给删除掉,保证数 
 * 据库的干净。如果没有显示的指定@Transactional,那么插入到数据库中的数据就是真实的插入了。
 */
@Transactional
public class XXXTests {  
    // xxxx
}