《学透Spring》

从入门到项目实战

数据资料: 《学透Spring

一、初识Spring

Spring FrameWorkSpring BootSpring CloudSpring Data

注意各模块的依赖版本支持情况,以及框架支持的JDK版本情况

Spring Initializr:官方InitializrAliyun Initializr

二、Spring FrameWork中的IoC容器

2.1 IoC容器基础知识

2.1.1 IoC容器

2.1.2 容器初始化步骤

步骤 内容
1 从xml文件、Java类或其他地方加载配置元数据
2 通过BeanFactoryPostProcessor对配置元数据进行一轮处理
3 初始化Bean实例,并根据给定的依赖关系组装对象
4 通过BeanPostProcessor对Bean进行处理,器件会粗发Bean被构造后的回调

xml文件方式初始化IoC容器

1
2
3
<beans xxx>
    <bean id="hello" class="learning.spring.helloworld.Hello" />
</beans>
1
2
3
4
5
6
7
8
// 根据xml文件初始化BeanFactory
BeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions("beans.xml");
// 从IoC容器中读取Bean(两种方式)
// Hello hello = (Hello) beanFactory.getBean("hello");
Hello hello = beanFactory.getBean("hello", Hello.class);
hello.sayHello();

2.1.3 BeanFactory与ApplicationContext

ApplicationContext继承BeanFactory,增加企业级应用所需的更多特性,通过ApplicationContext发挥Spring上下文的能力

1
2
3
4
5
// 根据xml文件初始化容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 从IoC容器读取Bean
Hello hello = applicationContext.getBean("hello", Hello.class);
hello.sayHello();

2.1.4 容器的继承

容器继承:子上下文可以看到父上下文定义的Bean,反之则不行;可以定义同ID的Bean,各自都能获取自己定义的Bean。

1
2
ApplicationContext parentContext = new ClassPathXmlApplicationContext("parent-beans.xml");
ApplicationContext childContext = new ClassPathXmlApplicationContext(new String[]{"parent-beans.xml"}, true, parentContext);

2.2 Bean基础知识

2.2.1 Bean

Bean是指Java中的`可重用软件组件`
  • Bean的名称
  • Bean的具体类信息,全限定类名
  • Bean的作用域,单例(每次都是同一个对象)还是原型(每次都是一个新对象)
  • 依赖注入相关信息,构造方法参数、属性以及自动织入方式
  • 创建销毁相关信息,懒加载模式、初始化回调方法和销毁回调方法

2.2.2 Bean的依赖关系

两种注入方式:基于构造方法的注入和基于Setter方法的注入

方法 java类模块 xml样例
构造方法 public Hello(String name){} <bean ...><constructor-arg value="李雷"/></bean>1
Setter方法 private String name <bean ...><property value="李雷"/></bean>

自动织入:自动织入方式可选no、byName、byType和constructor;多个候选Bean可选配置autowire-candidate为false、配置目标Bean注解@Primary

Bean初始化顺序<bean/>depends-on属性或代码的@DependsOn

2.2.3 Bean的三种配置方式

基于xml文件的配置

1
<bean id="xxx" class="learning.Spring.Yyy" factory-method="create" />

scope声明Bean作用域,lazy-init声明是否懒加载,depends-on声明初始化顺序

基于注解的配置

类别 详情
Bean创建 @Compent @Service @Repository @Controller (@RestController)
可注入依赖 @Autowire @Resource @Inject @Value(注入环境变量)

基于Java类的配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Java类的容器配置
@Configuration
@ComponentScan("learning.Spring") // 可省略,默认Config所在包
public class Config{
    @Bean
    @Lazy
    @Scope("prototype")
    public Hello helloBean(){return new Hello();}
}

// 初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);

2.3 定制容器与Bean的行为

2.3.1 Bean的生命周期

Bean的生命周期中可定制方法

初始化 销毁
添加了@PostConstrut的方法 添加了@PreDestory的方法
实现了InitializingBean的afterPropertiesSet()方法 实现了DisposableBean的destory()方法
<bean/>的init-method或@Bean中的initMethod <bean/>的destroy-method或@Bean中的destroyMethod

其中自动推测销毁Bean时的方法:

  1. public void close(){}public void shutdown
  2. 实现AutoClosableClosable接口的close()方法
1
2
3
4
5
6
7
public class  Hello implements DisposableBean{
    @PreDestroy
    public void shutdown(){}
    @Override
    public void destroy() throws Exception{}
    public void close(){}
}

2.3.2 Aware接口的应用

让Bean感知到容器的诸多信息

例如实现ApplicationContextAware接口用于在Bean中获取容器信息、实现ApplicationEventPublisherAware接口用于荣容器中获取ApplicationEventPublisher实例。

2.3.3 事件机制

容器变动,以ApplicationEvent的子类,在实现ApplicationListener的Bean中(或使用@EventListener)处理变更。

多个Bean处理同一个事件,通过order注解指定处理顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 方式1:实现ApplicationListener接口方式
@Component
@Order(1)
public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent>{
    @Override
    public void onApplicationEvent(ContextClosedEvent event){}
}
// 方式2:用注解@EventListener方式
@Component
public class ContextClosedEventAnnotationListener{
    @EventListener
    @Order(2)
    public void onEvent(ContextClosedEvent event){}
}

2.3.4 容器的拓展点

BeanPostProcessor:Bean的后置处理器接口(初始化前执行postProcessBeforeInitialization()、初始化后执行postProcessAfterInitiazation())

BeanFactoryPostProcessor:BeanFactory的后置处理器,BeanFactory加载所有Bean定义但未初始化时执行postProcessBeanFactory()方法

2.3.5 优雅的关闭容器

方式1:让Bean实现Lifecycle接口处理容器的变更;方式2:借助Spring FrameWork的事件机制

1
2
3
4
5
6
public class Hello implements Lifecycle{
    @Override
    public void start(){}
    public void stop(){}
    public boolean isRunning(){}
}

2.4 容器中的几种抽象

2.4.1 环境抽象

Profile抽象:

xml方式 代码方式 环境变量
配置 <beans/>的profile属性 @Profile注解在@Configuation注解的类上
@Profile注解在@Bean注解的方法上
启用 ConfigurableEnvironment.setActiveProfiles()指定激活的profile 配置文件指定spring.profiles.active环境变量的值

PropertySource抽象:添加属性来源

方式 样例
xml <context:property-placeholder location:"classpath:/META-INF/resources/app.properties" />
注解 @Configuration
@PropertySource("classpath:/META-INF/resources/app.properties")

2.4.2 任务抽象

异步执行: 统一的TaskExcutor方便配置多线程相关的细节

相关实现:同步的SyncTaskExcutor、SimpleAsyncTaskExecutor、ConcurrentTaskExecutor、ThreadPoolTaskExecutor

定时任务:统一的TaskSchduler方便处理特定时间执行和多次重复执行

xml方式:<task:scheduler id="taskScheduler" pool-size="10: />

代码方式:使用@EnableSchduling启用后使用@Schduled注解方法启用特定定时任务

三、Spring Framework中的AOP

3.1 Spring中的AOP

3.1.1 AOP的核心概念

AOP(Aspect Oriented Programming,面向切面编程)。

概念 说明
切面(aspect) 按关注点进行模块分解时,横切关注点就表示一个切面
连接点(join point) 程序执行的某一刻,在这个点上可以添加额外的动作
通知(advice) 切面在特定连接点上执行的动作
切入点(pointcut) 切入点是用来描述连接点的,他决定了当前代码与连接点是否匹配

3.1.2 Spring AOP的实现原理

Spring AOP的核心技术:动态代理技术(23种经典设计模式之一),在运行时动态的为对象创建代理的技术。

在Spring中,由AOP框架创建,用来实现切面的休想被成为AOP代理(AOP Proxy),一般采用JDK动态代理或者是CGLIB代理。

技术 必须要实现接口 支持拦截public方法 支持拦截protected方法 拦截默认作用域方法
JDK动态代理
CGLIB代理

JDK代理实现在调用前后打印内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 接口定义
public interface Hello{
    void say();
}
// 原始实现
public class SpringHello implements Hello{
    @Override
    public void say(){
        System.out.println("Hello Spring!");
    }
}
// 代理处理类
public class LogHandler implements InvocationHandler{
    private Hello source;
    public LogHanlder(Hello source){this.source = source;}
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        System.out.println("Ready to say somethind.");
        try{
            retrun method.invoke(source, args);
        } finally {
            System.out.println("Already say something.");
        }
    }
}
// 调用样例
public class Application{
    public static void main(String[] args){
        Hello orginal = new SpringHello();  // 原始实现
        Hello target = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(),
            original.getClass().getInterfaces(), new LogHanlder(original)); // 代理实现
        target.say();   // 发起代理调用
    }
}

使用代理模式中的小坑

如果存在一个类的内部方法调用,这个调用的对象不是代理,而是其本身,则无法享受AOP增强的效果。

1
2
3
4
5
6
7
public class Hello{
    public void foo(){
        bar();
    }
    public void bar(){}
}
// 哪怕AOP对bar做了拦截,由于调用的不是代理对象,因而看不到任何效果

3.2 基于@AspectJ的配置

核心步骤

步骤 说明
1 引入org.springframework:spring-aspects依赖
2 启用@AspectJ支持,注解形式@EnableAspectJAutoProxy2,xml形式<aop:aspcetj-autoproxy/>
3 声明切面,注解形式@Aspect

3.2.1 声明切入点

由两部分组成:切入点表达式(描述要匹配的连接点)和切入点方法签名(引用切入点,方便切入点的复用)。

切入点表达式注解@Pointcut及其常用的切入点标识符(pointcut designator,PCD34

PCD 说明
execution 最常用的一个PCD,用来匹配特定方法的执行
within 匹配特定范围内的类型,可以用通配符来匹配某个Java包内的所有类
this Spring AOP代理对象这个Bean本身要匹配某个给定的类型
target 目标对象要匹配某个给定的类型,比this更常用一些
args 传入的方法参数要匹配某个给定的类型,它也可以用来绑定请求参数
bean SpringAOP特有的一个PCD,匹配Bean的ID或名称,可以用通配符

针对注解的常用PCD

PCD 说明
@target 执行的目标对象带有特定类型的注解
@args 传入的方法参数带有特定类型注解
@annotation 拦截的方法上带有特定类型注解
1
execution([修饰符] <返回类型> [全限定类型.]<方法>(<参数>) [异常])

3.2.2 声明通知

同时存在多个通知作用域同一处,可是让切面实现Orderd接口或则添加@Order注解

前置通知@Before:方法没有返回值、可以对被拦截方法的参数进行加工

1
2
3
4
5
6
7
@Aspect
public class BeforeAspect{
    @Before("learning.spring.helloworld.HelloPointcut.sayHello()")
    public void before(){
        System.out.println("Before Advice");
    }
}

后置通知@AfterReturning@AfterThrowing@After:方法执行后,可能正常返回,也可能抛出异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@AfterReturning("execution(public * say(..))")
public void after(){}

@AfterReturning(pointcut = "execution(public * say(..))", returning = "words")
public void printWords(String words){
    // returning指定的字段自动绑定同名通知参数
}

@AfterThrowing("execution(public * say(..))")
public void afterThrow(){}

@AfterThrowing("execution(public * say(..))", throwing="exception")
public void afterThrow(Exception exception){
    // throwing指定的字段自动绑定同名通知参数
}

@After("execution(public * say(..))")
public void afterAdvice(){} // 不关心返回值

环绕通知@Around:在方法执行前后加入自己的逻辑、替换方法本身的逻辑、替换调用参数。第一个参数必须是ProceedingJoinPoint类型,方法的返回类型是被拦截方法的返回类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Around("execution(public * say(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable{
    long start = System.currentTimeMillis();
    try{
        return pjp.proceed();   // 对具体连接点进行处理
    }finally{
        long end = System.currentTimeMills();
        System.out.println("Total time: " + (end - start) + "ms");
    }
}

引入通知@DeclareParents:为Bean添加新的接口,并为新增的方法提供默认实现,这种操作称为引入。

1
2
3
4
5
6
@Aspect
public class MyAspect{
    @DeclareParents(value="learning.spring.helloworld.Hello",// 目标增强的Bean类型
        defaultImpl = DefaultGoodByeImpl.class) // 新增接口的方法的默认实现
    private GoodBye goodBye;    // 新增拓展的接口
}

3.2.3 基于@AspectJ的示例

声明原始Bean

1
2
3
4
5
6
7
8
public interface Hello{
    String sayHello(StringBuffer words);
}
@Component
public class SpringHello implements Hello{
    @Override
    public String sayHello(StringBuffer words){return "hello! " + words;}
}

声明切面:实现前置通知

1
2
3
4
5
6
7
8
9
@Aspect
@Component
@Order(1)
public class HelloAspect{
    @Before("target(learning.spring.helloworld.Hello) && args(words)")  // 绑定参数words
    public void addWords(StringBuffer words){
        words.append("Welcome to Spring!");
    }
}

第二个切面:实现后置通知、环绕通知(并调用引入的接口)和引入通知

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Aspect
@Component
@Order(2)
public class SayAspect{
    @DeclareParents(value="learning.spring.helloworld.*",// 目标增强的Bean类型
            defaultImpl = DefaultGoodBye.class) // 新增接口的方法的默认实现
    private GoodBye goodBye;    // 新增拓展的接口

    private int count = 0;

    @Beofre("excution(* say*(..)) && args(words)") // 绑定参数words
    public void countSentence(StringBuffer words){words.append("["+ ++count + "]\n");}

    @Around("excution(* say*(..)) && this(bye)") // 以GoodBye的类型绑定bye
    public String addSay(ProceedingJoinPoint pjp, GoodBye bye) throws Throwable{
        return pjp.proceed() + bye.sayBye();
    }

    public void reset(){counter = 0;}
    public int getCounter(){return counter;}
}

GoodBye接口及相关默认实现

1
2
3
4
5
6
7
public interface GoodBye{
    String sayBye();
}

public DefaultGoodBye implements GoodBye{
    public String sayBye(){return "Bye!";}
}

运行代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Configuration
@EnableAspectJAutoProxy
@ComponenctScan("learning.spring.helloworld")
public class Application{
    public static void main(String[] args){
        AnnotationConfigAplicationContext applicationConext = 
            new AnnotationConfigAplicationContext(Application.class);
        Hello hello = applicationContext.getBean("springHello", Hello.class);
        System.out.println(hello.sayHello(new StringBuffer("My Friend. ")));
        // Hello! My Friend. Welcome to Spring![1]\nBye!
        System.out.println(hello.sayHello(new StringBuffer("My Dear Friend. ")));
        // Hello! My Dear Friend. Welcome to Spring![2]\nBye!
    }
}

单元测试:Spring中单元测试依赖spring-test、junit-jupiter依赖,可配合maven-surefire-plugin插件,使用mvn test通过maven来执行测试

3.3 基于XML Schema的配置

Spring AOP相关的XML配置都放在<aop:config/>5

内容 关键XML
声明切入点 <aop:pointcut/>
前置通知 <aop:before/>
后置通知 <aop:after-returning/><aop:after-throwing/><aop:after/>
环绕通知 <aop:around/>
引入通知 <aop:declare-parents/>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<aop:config>
    <aop:aspect ref="helloAspect" order="1">
        <aop:before pointcut="target(learning.spring.helloworld.Hello) and args(words)"
            method="addWords"/>
    </aop:aspect>
    <aop:aspect ref="sayAspect" order="2">
        <aop:before pointcut="execution(* say*(..)) and args(words)" method="countSentence" />
        <aop:around pointcut="execution(* sayHello(..)) and this(bye)" method="addSay" />
        <aop:declare-parents types-mathcing="learning.spring.helloworld.*"
            implements-interface="learing.spring.helloworld.GoodBye"
            default-impl="learning.spring.hellowrold.DefaultGoodBye" />
    </aop:aspect>
</aop:config>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Application{
    public static void main(String[] args){
        AplicationContext applicationConext = 
            new ClassPathXmlApplicationContext("applicationContext.xml");
        Hello hello = applicationContext.getBean("springHello", Hello.class);
        System.out.println(hello.sayHello(new StringBuffer("My Friend. ")));
        // Hello! My Friend. Welcome to Spring![1]\nBye!
        System.out.println(hello.sayHello(new StringBuffer("My Dear Friend. ")));
        // Hello! My Dear Friend. Welcome to Spring![2]\nBye!
    }
}

四、从Spring Framework到Spring Boot

如果一个东西可以生成出来,那为什么还要生成它呢?

4.1 SpringBoot 基础知识

组成部分

组成 说明 备注
起步依赖 解决依赖管理难题 可通过mven dependcy:tree查看Maven的依赖信息
自动配置 为依赖提供常规的默认配置,消除模板化的配置
Spring Boot Actuator 提供一系列在生产环境运行时所需的特性
命令行CLI

两种引入springboot的方式

继承starter-parent 无法继承starter-parent
引入方式 <parent> <dependency>
打包插件 引入即可,版本已在starter中定义 引入spring-boot-maven-plugin并指定版本

4.2 起步依赖

springboot内置的起步依赖:spring-boot-starter开头(应避免使用这个前缀)

名称 描述
spring-boot-starter 核心功能,比如自动配置、配置加载等
spring-boot-starter-actuator 各种生产级特性
spring-boot-starter-aop Spring AOP相关支持
spring-boot-starter-data-jpa Spring Data JPA相关支持,默认使用Hibernate作为JPA实现
spring-boot-starter-data-redis Spring Data Redis相关支持,默认使用Lettuce作为Redis客户端
spring-boot-starter-jdbc Spring的jdbc支持
spring-boot-starter-logging 日志依赖,默认使用Logback
spring-boot-starter-security Spring Security相关支持
spring-boot-starter-test 在Spring项目中进行测试所需的相关库
spring-boot-starter-web 构建Web项目所需的各种依赖,默认使用Tomcat作为内嵌容器

如果想要升级依赖的版本,可在org.springframework.boot:spring-boot-dependencies的pom.xml中寻找相关的版本声明,或直接在<dependencies/>中直接引入所需的依赖,同时在起步依赖中排除所加的依赖。

异步依赖的实现原理:Maven的依赖传递机制,在Maven的<dependencyManagement/>中统一定义依赖的信息(比如版本、排除的传递依赖项等),随后在<dependencies/>中添加依赖就不用重复配置这些信息。

4.3 自动配置

@SpringBootApplication注解中添加了@EnableAutoConfiguration注解,开启了自动配置功能。

4.3.1 自动配置的实现原理

实现原理:

自动配置类其实就是添加了@Configuration的普通Java配置类,例如加入了条件注解@Conditional来实现“根据特定条件启用相关配置类”。附:SpringBoot条件注解的底层实现原理

引入不在扫描路径中的自动配置类:

秘密在于@EnableAutoConfiguration的@Import(AutoConfigurationImportSelector.class),通过SpringFactoiesLoader加载/META-INF/spring.factories里配置的自动配置类列表。

禁用某些自动配置:

  1. 在配置文件中使用spring.actuoconfig.exclude配置项,值是要排除的自动配置类的全限定类名
  2. 在@SpringBootApplication注解中添加exclude配置,值是要排除的自动配置类。

4.3.2 配置项加载机制详解

0%