数据资料: 《学透Spring》
一、初识Spring
Spring FrameWork、Spring Boot、Spring Cloud、Spring Data
注意各模块的依赖版本支持情况,以及框架支持的JDK版本情况
Spring Initializr:官方Initializr和Aliyun 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> |
| 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时的方法:
public void close(){}或public void shutdown
- 实现
AutoClosable或Closable接口的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支持,注解形式@EnableAspectJAutoProxy,xml形式<aop:aspcetj-autoproxy/> |
| 3 |
声明切面,注解形式@Aspect |
3.2.1 声明切入点
由两部分组成:切入点表达式(描述要匹配的连接点)和切入点方法签名(引用切入点,方便切入点的复用)。
切入点表达式注解@Pointcut及其常用的切入点标识符(pointcut designator,PCD)
| 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/>中
| 内容 |
关键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里配置的自动配置类列表。
禁用某些自动配置:
- 在配置文件中使用spring.actuoconfig.exclude配置项,值是要排除的自动配置类的全限定类名
- 在@SpringBootApplication注解中添加exclude配置,值是要排除的自动配置类。
4.3.2 配置项加载机制详解