摘要
本文依照图灵教育出版的《学透Spring》记录了学习Spring所需要的难重点。
第一章 IoC容器
一、IoC容器基础
1.控制反转
控制反转是一种决定容器如何装配组件的模式。只要遵循这种模式,按照一定的规则,容器就能将组件组装起来。这里所谓的容器就是用来创建组件并对它们进行管理的地方。
2.IoC容器
IoC容器即所有的组件都要被动接受容器的控制。
![](https://img-blog.csdnimg.cn/4524fb61f10a4693ab8e4f8e96ba6093.png)
3.容器的初始化
- 从XML文件、Java类或其他地方加载配置元数据。
- 通过BeanFactoryPostProcessor对配置元素进行一轮处理。
- 初始化Bean实例,并根据给定的依赖关系组装对象。
- 通过BeanPostProcessor对Bean进行处理,期间还会出发Bean被构造后的回调方法。
加载配置⽂件并执⾏的 Application 类代码⽚段
public class Application {
private BeanFactory beanFactory;
public static void main(String[] args) {
Application application = new Application();
application.sayHello();
}
public Application() {
beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((DefaultListableBeanFactory)beanFactory);
reader.loadBeanDefinitions("beans.xml");
}
public void sayHello() {
// Hello hello = (Hello) beanFactory.getBean("hello");
Hello hello = beanFactory.getBean("hello", Hello.class);
System.out.println(hello.hello());
}
}
4.BeanFactory与ApplicationContext
BeanFactory是容器的基础接口,ApplicationContext接口继承了BeanFactory。在绝大多数情况下,应使用ApplicationContext的实现类。
类名 | 说明 |
---|---|
ClassPathXMLApplicationContext | 从CLASSPATH中加载XML文件来配置ApplicationContext |
FileSystemXMLApplicationContext | 从文件系统中加载XML文件来配置ApplicationCOntext |
AnnotationConfigApplicationContext | 根据注解和Java类配置ApplicationContext |
从BeanFactory修改为使用ApplicationContext(beans.xml为xml中配置的bean,代码未给出):
public class Application {
private ApplicationContext applicationContext;
public static void main(String[] args) {
Application application = new Application();
application.sayHello();
}
public Application() {
applicationContext= new ClassPathXmlApplicationContext("beans.xml");
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((DefaultListableBeanFactory)beanFactory);
reader.loadBeanDefinitions("beans.xml");
}
public void sayHello() {
// Hello hello = (Hello) beanFactory.getBean("hello");
Hello hello = beanFactory.getBean("hello", Hello.class);
System.out.println(hello.hello());
}
}
5.容器继承关系
子容器可以继承父容器中配置的组件。
容器继承 | Java类继承 |
---|---|
子上下文可以看到父上下文中定义的Bean,反之则不行 | 子类可以看到父类的protected和public属性和方法,父类看不到子类的 |
子上下文中可以定义与父类上下文同ID的Bean,各自都能获取自定义的Bean | 子类可以覆盖父类定义的属性和方法 |
关于同ID覆盖Bean,有时也会引发一些意料之外的问题。可以使用setAllowbeanDefinitionOverriding()方法控制开启和关闭。
二、Bean基础知识
JavaBeans是Java中一种特殊的类,可以将多个对象封装到一个对象(Bean)中。名称中的Bean是用于Java的可重用软件的管用叫法。Spring容器对Bean并没有太多的要求,无需实现特定接口或依赖特定库,只要是最普通的Java对象即可,这类对象也被称为POJO(Plain Old Java Object)。
1.Bean的依赖关系
所谓“依赖注入”,很重要的一块就是管理依赖。在Spring容器中,“管理依赖”主要就是管理Bean之间的依赖。有两种基本的注入方式——基于构造方法的注入和基于Setter方法的注入。
构造方法的注入对应的XML配置文件使用<constructor-arg/>
<bean id="hello" class="learning.spring.helloworld.Hello">
<constructor-arg value="Spring"/>
</bean>
属性 | 作用 |
---|---|
value | 要传给构造方法参数的值 |
ref | 要传给构造方法参数的BeanID |
type | 构造方法参数对应的类型 |
index | 构造方法参数对应的位置,从0开始计算 |
name | 构造方法参数对应的名称 |
基于Setter方法的引入:
<bean id="..." class="...">
<prperty name="xxx">
<!-- 直接定义一个内部的Bean -->
<bean class="...">
</prperty>
<prperty name="yyy">
<!-- 定义一个列表 -->
<list>
<value>
aaa
</value>
</list>
</prperty>
<prperty>
<!-- 定义依赖的Bean -->
<ref bean="..."/>
</prperty>
</bean>
Spring容器可以替我们自动织入,自动织入有几种模式:
名称 | 说明 |
---|---|
no | 不进行自动织入 |
byName | 根据属性名查找对应的Bean进行自动织入 |
byType | 根据属性类型查找对应的Bean进行织入 |
constructor | 同byType,但用于构造方法注入 |
2.Bean的三种配置方式
(1)基于XML文件的配置
<bean id="..." class="..." scope="singleton" lazy-init="true" depends-on="xxx"/>
scope表明当前Bean是单例还是原型,lazy-init是指当前Bean是否懒加载的,depends-on明确指定当前Bean的初始化顺序。
(2)基于注解的配置
Spring framework包含了@Required、@Autowired、@Component、@Service、@Repository
启用基于注解的配置
……
<context:component-scan base-package="learning.spring"/>
……
注解 | 说明 |
---|---|
@Component | 将类标识为普通的组件,即一个Bean |
@Service | 将类标识为服务层的服务 |
@Repository | 将类标识为数据层的数据仓库,一般是DAO(Data Access Object) |
@Controller | 将类标识为Web层的Web控制器(后来针对REST服务又增加 @RestController注解) |
可以选择指定Bean名称或者Spring容器会自动生成一个。
@Component("hello")
public class Helloe(){...}
注解 | 说明 |
---|---|
@Autowired | 根据类型注入依赖,可用于构造方法,Setter方法和成员变量 |
@Resource | JSR-250的注解,根据名称注入依赖 |
@Inject | JSR-330的注解,同@Autowired |
(3)基于Java类的配置
使用@Configuration、@Bean、@CopmponentScan注解
@Configuration
@ComponentScan(basePackages="org.example"),
includeFilters=@Filter(type=FilterType.REGEX,
pattern=".*Stub.*Repository"),
excludeFilters=@Filter(Repository.class))
puiblic class AppConfig{
@Bean
@Lazy
@Scope("prototype")
public Hello helloBean(){
return new Hello();
}
}
类上的@Configuration注解表明这是一个Java配置类,@ComponentScan注解指定了类扫描的包名,作用与<context:component-scan/>类似。在@ComponentScan中,includeFilters和excludedFilters可以用来指定包含和排除的组件。
如果@Configuration没有指定扫描的基础包路径或者类,默认就从该配置类的包开始扫描。
Config类中的helloBean()方法上添加了@Bean注解,该方法的返回对象会被当做容器中的一个Bean,@Lazy注解说明这个Bean是延时加载的,@Scope注解则指定了它是原型Bean。
属性 | 默认值 | 说明 |
---|---|---|
name | {} | Bean的名称,默认同方法名 |
value | {} | 同name |
autowire | Autowire.NO | 自动织入方式 |
autowireCandidate | true | 是否是自动织入的候选Bean |
initMethond | "" | 初始化方法名 |
destroyMethond | AbstractBeanDefinition.INFER_METHOD | 销毁方法名 |
@Configuration
public class Config(){
@Bean
public Foo foo(){
return new Foo();
}
@Bean
public Bar bar(Foo foo){
return new Bar(foo);
}
@Bean
public Baz baz(){
return new Baz(foo());
}
}
以上代码foo()创建了一个名为foo的Bean,bar()方法通过参数foo注入了foo这个Bean,baz()方法内则通过调用foo()获得了同一个Bean。
在配置类中可以使用@Import导入其他配置类,如:
@Configuration
@Import({ConfigA.class, ConfigB.class})
@ImportResoure("classpath:/spring/*-applicationContext.xml")
public class Config{}
3.定制容器与Bean的行为
(1)Bean的生命周期
(2)创建Bean后的回调动作
InitializingBean接口有一个afterProperiesSet()方法就是在所有依赖都注入后自动调用该方法。在方法上添加@PostConstruct注解也有相同的效果。
也可以在XML文件中进行配置:
<bean id="hello" class="learning.spring.helloworld.Hello" init-method="init"/>
或者在Java配置中指定:
@Bean(initMethod="init")
public Hello hello(){
...
}
(2)销毁Bean前的回调动作
DisposableBean接口中的destory()方法和添加了@PreDestory注解的方法都能实现。也可以在<bean/>中指定destory-method,或者在@Bean中指定destoryMethod。
public class Hello implements DisposableBean {
public String hello() {
return "Hello World!";
}
@Override
public void destroy() throws Exception {
System.out.println("See you next time.");
}
}
Spring会按照添加了@PostConstruct或@PreDestroy的方法->实现了接口的方法->在<bean/>中配置的顺序依次进行调用。
4.Aware接口
如果希望在Bean中获取容器信息,可以通过如下两种方式:
- 实现BeanFactoryAware或ApplicationContextAware接口;
- 用@Autowired注解来注入BeanFactory或ApplicationContext
实际上两种方式的本质都是一样的,即让容器注入一个BeanFactory或ApplicationContext对象。在拿到ApplicationContext后,就能操作该对象,比如调用getBean()方法取得想要的Bean。
5.事件机制
ApplicationContext提供了一套事件机制,在容器发生变动时我们可以通过ApplicationEvent的子类通知到ApplicationListener接口的实现类,最对应的处理。例如,ApplicationContext在启动、停止、关闭和刷新时,分别会发出ContextClosedEvent、ContextStopEvent、ContextCloseEvent和ContextRefreshedEvent事件。
也可以自己监听这些事件,只需要实现ApplicationListener接口或者在某个Bean的方法上增加@EventListener注解。
6.容器的扩展点
BeanPostProcessor接口是用来定制Bean的,这个接口是Bean的后置处理器,在Spring容器初试化Bean时可以加入自己的逻辑。
该接口中有两个方法,postProcessBeforeInitialization()方法在Bean初始化前执行,postProcessAfterInitialization()方法在Bean初试化后执行。如果有多 个 BeanPostProcessor,可以通过 Ordered 接⼝或者 @Order 注解来指定运⾏的顺序。
Spring AOP也是通过BeanPostProcessor实现的,因此实现该接口的类,以及其中直接引用的Bean都会被特殊对待,不会被AOP增强。此外,BeanPostProcessor和BeanFactoryPostProcessor都仅对当前容器上下文的Bean有效,不会去处理其他上下文。
7.关闭容器
Lifecycle用来感知容器的启动和停止,容器会将启动和停止的信号传播给实现了该接口的组件和上下文。
public class Hello implements Lifecycle {
private boolean flag = false;
public String hello() {
return flag ? "Hello World!" : "Bye!";
}
@Override
public void start() {
System.out.println("Context Started.");
flag = true;
}
@Override
public void stop() {
System.out.println("Context Stopped.");
flag = false;
}
@Override
public boolean isRunning() {
return flag;
}
}
@Configuration
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);
applicationContext.start(); // 这会触发Lifecycle的start()
Hello hello = applicationContext.getBean("hello", Hello.class);
System.out.println(hello.hello());
applicationContext.close(); // 这会触发Lifecycle的stop()
System.out.println(hello.hello());
}
@Bean
public Hello hello() {
return new Hello();
}
}
除此之外,我们还可以借助Spring Framework的事件机制,在上下文关闭时会发出ContextClosedEvent,监听该事件也可以出发业务层代码做对应的操作。
8.容器中的几种抽象
(1)环境抽象
Profile抽象,PropertySource抽象
(2)任务抽象
异步执行、定时任务
第二章 AOP
一、AOP基础
1.AOP的概念
Aspect Oriented Programming即面向切面编程。是一种编程范式,它的目的是通过分离横切关注点(cross-cutting concerns)来提升代码模块化程度。AOP中提到的关注点,其实就是一段特定的功能,有些关注点出现在多个模块中,就称为横切关注点。
AOP解决了两个问题:第一是代码混乱,核心的业务代码还必须兼顾其他功能,这就导致不同功能的代码交织在一起,可读性差;第二是代码分散,同一个功能的代码分散在多个模块中,不易维护。
实际上AOP同OOP(Object-Oriented Programming,面向对象编程)一样,都是一种编程范式,但它并非站在OOP的对立面,而是对OOP的一个很好的补充。Spring Framework很好的将二者融合在了一起。
概念 | 说明 |
---|---|
切面(aspect) | 按关注点进行模块分解时,横切关注点就表示为一个切面 |
连接点(join point) | 程序执行的某一时刻,在这个点上可以添加额外的动作 |
通知(advice) | 切面在特定的连接点上执行的动作 |
切入点(pointcut) | 切入点是用来描述连接点的,它决定了当前代码与连接点是否匹配 |
通过切入点来匹配程序中特定的连接点,在这些连接点上执行通知,这种通知可以是在连接点前后执行,也可以是将连接点包围起来。
2.Spring AOP的实现原理
核心技术采用了动态代理技术
动态代理就是在运行时动态地为对象创建代理的技术。在Spring中,由AOP框架创建、用来实现切面的对象称为AOP代理(AOP Proxy)。
Spring容器在为Bean注入依赖时,会自动将被依赖Bean的AOP代理注入进来,被切面拦截的称为目标对象(target object)或通知对象(advised object),因为Spring用了动态代理,所以目标对象就是要被代理的对象。
3.基于@AspectJ的配置
Spring Framework同时支持@AspectJ注解和XML Schema两种方式来使用AOP。要开启@AspectJ支持,可以在Java配置类上增加@EnableAspectJAutoProxy注解。
@Configuration
@EnableAspectJAutoProxy
public class Config{
...
}
@EnableAspectJAutoProxy有两个属性,proxyTargetClass用于选择是否开启基于类的代理;exposeProxy用于选择是否将代理对象暴露到AopContext中,两者默认值都是false。
也可以通过XML Schema的方式来实现相同的效果:
<aop:aspectj-autoproxy/>
完成以上配置后,可以使用@Aspect注解来声明切面,将注解加到类上:
@Aspect
public class MyAspect{...}
注意两点内容:
- 添加@Aspect注解只是告诉Spring“这个是切面”,但并没有把它声明为Bean,因此需要手动进行配置,例如添加@Component注解,或者Java配置类中进行声明。
- Spring Framework会对带有@Aspect注解的类做特殊对待,因为其本身就是一个切面,所以不会被别的切面自动拦截。
4.声明切入点
注解方式的切入点声明由两部分组成——切入点表达式和切入点方法签名。
public class HelloPointcut{
@Pointcut("target(learning.sppring.hellowrld.Hello)")
public void helloType(){}
@Pointcut("execution(public * say())")
public void sayOperation(){}
@Pointcut("helloType() && sayOperation()")
public void sayHello(){}
}
PCD | 说明 |
---|---|
execution | 最常用的一个PCD,用来匹配特定方法的执行 |
within | 匹配特定范围内的类型,可以用通配符来匹配某个Java包内的所有类 |
this | Spring AOP代理对象这个Bean本身要匹配某个给定的类型 |
target | 目标对象要匹配某个给定的类型,比this更常用一些 |
args | 传入的方法参数要匹配某个给定的类型,它也可以用于绑定请求参数 |
bean | Spring AOP特有的一个PCD,匹配Bean的ID或名称,可以用通配符 |
execution([修饰符] <返回类型> [全限定类名.]<方法>(<参数> [异常])
其中,
- 每个部分都可以使用*通配符
- 类名中使用.*表示包中的所有类,..*表示当前包与子包中的所有类
- 参数主要分为以下几种情况:
- ()表示方法无参数
- (..)表示有任意个参数
- (*)表示有一个任意类型的参数
- (String)表示有一个String类型的参数
- (String, String)代表有两个String类型的参数
PCD | 说明 |
---|---|
@target | 执行的目标对象带有特定类型注解 |
@args | 传入的方法参数带有特定类型注解 |
@annotation | 拦截的方法上带有特定类型注解 |
// learning.spring.helloworld及其⼦包中所有类⾥的say⽅法
// 该⽅法可以返回任意类型,第⼀个参数必须是String,后⾯可以跟任意参数
execution(* learning.spring.helloworld..*.say(String,..))
// learning.spring.helloworld及其⼦包
within(learning.spring.helloworld..*)
// ⽅法的参数仅有⼀个String
args(java.lang.String)
// ⽬标类型为Hello及其⼦类
target(learning.spring.helloworld.Hello+)
// 类上带有@AopNeeded注解
@target(learning.spring.helloworld.AopNeeded
5.声明通知
可以在方法执行前、返回后、抛出异常后添加特定的操作。
(1)前置通知
在被拦截到的方法开始执行前,会先执行通知中的代码:
@Aspect
public class BeforeAspect{
@Before("learning.spring.helloworld.HelloPointcut.sayHello()")
public void before(){
System.out.rintln("Before Advice");
}
// 同一个切面类里还可以有其他通知方法
// 这就是一个普通的Java类,没有太多限制
}
前通知没有返回值,因为他在被拦截的方法前执行,就算有返回值也没地方使用,但是它可以对被拦截方法的参数进行加工,通过args这个PCD能明确参数,并将其绑定到前置通知方法的参数上。例如,要在sayHello(AtomicInteger)这个方法前对AomicInteger类型的参数进行参数调整。
@Befor("... && arg(cout)")
public void before(AtomicInteger cout){
// 操作count
}
可以使用@Order注解指定值的优先级,值越低,优先级则越高。
(2)后置通知
如果想要拦截正常返回的调用,可以使用@AfterReturing注解。
@AfterReturning("execution(public * say(...))")
public void after(){}
@AfterReturning(pointcut="execution(public * say(...))", returning="words")
public void printWords(String words){
System.out.println("Say Something: " + words);
}
printWords()方法的参数words就是被拦截方法的返回值,而且此处限定了该通知只拦截返回值是String类型的调用。需要提醒的是,returning中给定的名字必须与方法的参数名保持一致。
如果想拦截抛出异常的调用,可以使用@AfterThrowing注解,这个注解用法与@AfterReturing极为类似。
@AfterThrowing("execution(public * say(..))")
public void afterThrow() {}
@AfterThrowing(pointcut = "execution(public * say(..))", throwing = "exception")
public void printException(Exception exception) {}
如果不关注执行是否成功,只想在方法结束后做些动作,可以使用@After注解:
@After("execution(public * say(...))")
public void afterAdvice(){}
(3)环绕通知
在方法执行前后加入自己的逻辑,也可以完全替换方法本身的逻辑,或者替换调用参数。可以添加@Around注解来声明环绕通知,这个方法的签名需要特别注意,它的第一个参数必须是ProceedingJoinPoint类型的,方法的返回类型是被拦截方法的返回类型,或者直接用Object类型。
@Aspect
public class TimerAspect{
@Around("execution(public * say(...))")
public Object recordTime(ProceedJoinPoint pjp) throws Throwable{
long start = System.currentTimeMillis();
try {
return pjp.proceed():
} finally {
long end = System.currentTimeMillis();
System.out.println("Total time: " + (end - start) + "ms");
}
}
}
其中pjp.proceed()就是调用具体的连接点进行的处理,proceed()方法也接受Object[]参数,可以替代原先的参数。
(4)引入通知
可以为Bean添加新的接口,并为新增的方法提供默认实现,这种操作被称为引入。
@Aspect
public class MyAspect{
@DeclarParets(value="learing.spring.helloworld.Hello+", defaultImmpl=DefaultGoodByeImpl.class)
private GoodBye goodBye;
}
引入其实是针对类型进行的增强,value中仅可填入要匹配的类型,可以使用AspectJ类型匹配模式。引入声明后,在Spring容器中取到的Bean就已经完成了增强,哪怕在前置通知中也是如此。
二、@AspectJ示例
Hello接口及其实现代码片段
public interface Hello{
String sayHello(String wrds);
}
@Component
public class SpringHello implements Hello{
@Overide
public String sayHello(StringBuffer words){
return "Hello!" + words;
}
}
HelloAspect切面代码片段
@Aspect
@Component
@Order(1)
public class HelloAspect {
@Before("target(learning.spring.helloworld.Hello)&&arg(words)")
public void addWords(StringBuffer words) {
words.append("Welcome to Spring!");
}
}
三、基于XML Schema的配置
声明切面:
<aop:config>
<aop:aspect id="helloAspect" ref="aspectBean">
</aop:aspect>
</aop:config>
声明切入点:
<aop:config>
<aop:aspect id="helloAspect" ref="aspectBean">
<aop:pointcut id="helloType" expression="target(learning.spring.hellowrld.Hello)"/>
<!-- 其他内容省略 -->
</aop:aspect>
</aop:config>
声明通知:
(1)前置通知
<aop:aspect id="beforeAspect" ref="beforeAspectBean">
<aop:before pointcut="learning.spring.helloworld.HelloPointcut.sayHello()" method="before"/>
</aop:aspect>
(2)后置通知
<aop:after-returning pointcut="execution(public * say(..))" returning="words" method="printWords" />
<aop:after-throwing pointcut="execution(public * say(..))" method="afterThrow" />
<aop:after-throwing pointcut="execution(public * say(..))" throwing="exception" method="printException" />
<aop:after pointcut="execution(public * say(..))" method="afterAdvice" />
(3)环绕通知
<aop:around pointcut="execution(public * say(..))" method="recordTime" />
(4)引入通知
<aop:aspect id="myAspect" ref="myAspectBean">
<aop:declare-parents types-matching="learning.spring.helloworld.Hello+" implement-interface="learning.spring.helloworld.GoodBye" default-impl="learning.spring.helloworld.DefaultGoodByeImpl"/>
<!-- 其他省略 -->
</aop:aspect>
(5)通知器
<aop:config>
<aop:pointcut id="sayMethod" expression="execution(public * say(..))" />
<aop:advisor pointcut-ref="sayMethod" advice-ref="aroundAdvice" />
</aop:config>
<bean id="aroundAdvice" class="learning.spring.helloworld.SayMethodInterceptor"/>
第三章 SpringBoot
1.SpringBoot基础
springboot提供了大量功能,主要核心是以下几点:
- 起步依赖
- 自动配置
- SpringBoot Actuator
- 命令行CLI
|-pom.xml
|-src
|-main
|-java
|-resources
|-test
|-java
|resources
2.起步依赖
所引入的依赖不会冲突。
3.内置的起步依赖
![](https://img-blog.csdnimg.cn/a8f406e4a7234f6996ce59d898f56203.png)
起步依赖背后使用的其实就是Maven的传递依赖机制。POM文件分为如下三个部分:
- 起步依赖本身的描述信息
- 导入依赖管理项
- 具体依赖项
4.自动配置
自动配置类其实就是添加了@Configuration的普通java配置类,他利用Spring Framework 4.0加入的条件注解@Conditional来实现“根据特定条件启用相关配置类”,注解中传入的Condition类就是不同条件的判断逻辑。
配置项加载机制
(1)SpringBoot的属性加载优先级
SpringBoot有18种方式来加载属性。
(2)SpringBoot的配置文件
SpringBoot会按照如下优先级加载属性:
- 打包后的Jar包以外的application-{profile}.properties
- 打包后的Jar包以外的application.properties
- Jar包内部的application-{profile}.properties
- Jar包内部的application.properties
(3)类型安全的配置属性
通常,我们会在类中用@Value("{}")注解来访问属性,或者在XML文件中使用${}占位符。在配置中,可能会有大量的属性需要一一对应到类的成员变量上,SpringBoot提供了一种结构化且类型安全的方式来处理配置属性(configuration properties)——使用@ConfigurationProperties注解。
5.自己的自动配置配置和起步依赖
(1)编写自己的自动配置
编写自己的自动配置只需要三个步骤:
- 编写常规的配置类
- 为配置类添加条件与顺序
- 在/META-INF/spring.factories文件中添加自动配置类。
(2)脱离Spring Boot实现自动配置
第四章 面向生产的Spring Boot
一、SpringBoot Actuator
1.端点概览
根据功能不同可将端点分为四类:
信息类端点、监控类端点、监控类端点、操作类端点
2.端点配置
Spring Boot Actuator非常灵活,它提供了大量的开关配置,还包括了Http访问端点和JMX访问端点。
3.定制端点信息
(1)定制info端点信息
(2)定制health端点信息
健康检查是一个很常用的功能,可以帮助我们了解系统的健康状况,例如,系统在启动后是否准备好了对外提供服务了,所依赖的组件是否已就绪等。
4.开放自己的组件与端点
SpringBoot Actuator提供了让我们自己扩展端点或者实现新端点的功能。例如,在进行健康检查时,我们可以加入自己的检查逻辑,只需要实现HealthIndicator即可。
(1)开发自己的HealthIndicator
为了增加自己的健康检查逻辑,我们可以定制一个HealthIndicator实现,通常选择扩展AbstractHealthIndicator类,实现其中的doHealthCheck()方法。
(2)开发自己的端点
如果内置的端点无法满足我们的需求,可以使用SpringBoot Actuator实现一个端点。
二、基于Micrometer的系统度量
1.Micrometer概述
Micrometer的目标就是称为度量界的Slf4j。
2.常用的度量指标
SpringMVC、HTTP客户端、数据源
3.自定义度量指标
- 注入Spring上下文中的MeterRegistry,通过它来绑定Meter;
- 让Bean实现MeterBinder,在其bindTo()方法中绑定Meter。
SpringBoot Actuator中可以对Micrometer的度量指标做很多定制,既可以按照Micrometer的官方做法用MeterFilter精确地进行调整,也可以简单地使用配置来做些常规改动。
4.度量值的输出
(1)输出到日志
Micrometer提供了几个基本的MeterRegistry,其中之一就是LoggingMeterRegistry,它可以定时将系统中大的各个度量指标输出到日志中。
(2)输出到Prometheus
将度量信息直接输出到监控系统也是一种常见做法。
三、部署SpringBoot应用程序
1.可执行Jar及其原理
mvn clean package -Dmaven.test.skip
java -jar target/binarytea-0.0.1-SHAPSHOT.jar
Jar包内容有一下几部分组成:
- META-INF,工程元数据,例如Maven的表述文件与spring.factories文件;
- org/springframework/boot/loader,SpringBoot用来引导工程启动的Loader相关类;
- BOOT-INF/classes,工程自身的类与资源文件;
- BOOT-INF/lib,工程所依赖的各种其他Jar文件。
SpringBoot中还有一种更激进的方式,生成可以直接在Linux中运行的Jar文件,不需要java -jar命令(这个文件不含JRE),修改spring-boot-maven-plugin配置的方式如下:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
此外,GraalVM的出现可以提供更好的体验。
2.构建启动代码
(1)自定义SpringApplication
(2)通过FailureAnalyzer提供失败原因分析
(3)自定义Banner栏
3.启动后的一次性执行逻辑
SpringBoot提供了两个接口,分别是ApplicationRunner与CommandLineRunner,他们功能是基本相同的,只是方法参数不同。CommandLineRunner的run(String... arg)传入的参数与main(String ...arg)相同。⽽通过 ApplicationArguments,我们可以更⽅便灵活地控制命令⾏中的参数。如果 Spring 上下⽂中存在多个 ApplicationRunner 或 CommandLineRunner Bean, 可以通过 @Order 注解或 Ordered 接⼝来指定运⾏的顺序。
至此Spring入门部分完成,接下来的就是Spring的数据操作以及更深入的操作。
第五章 Spring中的JDBC
一、配置数据源
1.数据库连接池
(1)HikariCP
HikariCP是SpringBoot 2.x项目默认的数据库连接池,官方一直将“快”作为自己的亮点。从官方性能测试的结果来看HikaroCP的性能将数倍于DBCP2、C3P0、C3P0和Tomcat连接池。
- 通过字节码进⾏加速,JavassistProxyFactory 中使⽤ Javassist 直接⽣成了⼤量字节码塞到了 ProxyFactory 中,同时 还对字节码进⾏了精确地优化;
- 使⽤ FastList 代替了 JDK 内置的 ArrayList;
- 从 .NET 中借鉴了⽆锁集合 ConcurrentBag。
(2)Druid
Druid 的特点应该就是“全”,仅其内置的功能就已经能满⾜ 绝⼤部分⽣产环境中的苛刻要求了,更不⽤说我们还能对它进⾏扩展。
2.数据源配置详解
在配置数据源时主要使用了三项参数:
配置项 | 说明 |
---|---|
spring.datasource.url | 数据库的JDBC URL |
spring.datasource.user.name | 连接数据库的用户名 |
spring.datasource.password | 连接数据库的密码 |
通过DataSourceAutoConfiguration类可以实现数据库的自动配置,整个DataSourceAutoConfiguration分为两个内嵌配置类——内嵌数据库配置类EmbeddedDatabaseConfiguration和连接池数据源配置类PooledDataSourceConfiguration。
要连接数据库(以MySql为例),首先需要在pom.xml的<dependencies/>中加入MySql的JDBC驱动,可以添加下面这样的依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
在application.properties中配置MySql数据源:
spring.datasource.url=jdbc:mysql://localhost/binary-tea?
useUnicode=true&characterEncoding=utf8
spring.datasource.username=binary-tea
spring.datasource.password=binary-tea
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=10
使用如下代码可以实现MySql的连接:
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(properties.getUrl());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
二、事务管理
1.Spring Framework的事务抽象
以本地数据源的事务为例,可以像下面这样来配置DataSourceTransactionManager:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
用来描述事务定义的 TransactionDefinition接口中包含了几个与事务密切相关的属性:
- 传播性
- 隔离级别
- 超时时间
- 是否只读
2.SpringBoot的事务配置
在配置类上添加 @EnableTransactionManagement 注解就能开启事务⽀持。
3.Spring声明式事务
(1)基于注解的方式
注解中可以设置很多种事务属性。要开启事务除了使用DataSourceTransactionManager以外,还可以使用xml配置。
<tx:annotation-driven transaction-manager="txManager"/>
顺便提一下通常事务加在那一层比较合适:
通常情况下,我们会对应⽤进⾏分层,划分出 DAO 层、Service 层、View 层等。如果了解过领域驱动设计(Domain-Driven Design,DDD),就会知 道其中也有 Repository 和 Service 的概念。⼀次业务操作⼀般都会涉及多张 表的数据,因此在单表的 DAO 或 Repository 上增加事务,粒度太细,并不 能实现业务的要求。⽽在对外提供的服务接⼝上增加事务,整个事务的范围 ⼜太⼤,⼀个请求从开始到结束都在⼀个⼤事务⾥,着实⼜有些浪费。 所以,事务⼀般放在内部的领域服务上,也就是 Service 层上会是⽐较常⻅ 的⼀个做法,其中的⼀个⽅法,也就对应了⼀个业务操作。
(2)基于XML的方式
Spring提供了一系列<tx/>的XML来配置事务相关的AOP通知。有了AOP通知后,就可以像普通的AOP配置那样对方法的执行进行拦截和增强了。其中<tx:advice/>用来配置事务通知,如果事务管理器的名字是transactionManager。那就可以不⽤设置 transaction-manager 属性 了。具体的事务属性则通过 和 来设置。 可供设置的属性和 @Transactional 注解的基本⼀样。
4.Spring编程式事务
三、异常处理
1.统一的异常抽象
![](https://img-blog.csdnimg.cn/1312b95a6b094c90862e88f91d2d4793.png)
2.自定义错误码处理逻辑
第六章 对象给关系映射
一、通过Hibernate操作数据库
新的SSH为Spring Framework、Spring MVC与Hibernate。
二、通过MyBatis操作数据库
1.定义Mybatis映射
(1)通过注解定义常用操作映射
注解 | 作用 | 重点属性说明 |
---|---|---|
@Insert | 定义插入操作 | value 为具体使⽤的 SQL 语句 |
@Delete | 定义删除操作 | 同上 |
@Update | 定义更新操作 | 同上 |
(2)自定义类型映射
MyBatis 是通过 TypeHandler 来实现特殊类型的处理的。
@Result(column = "size", property = "size", typeHandler = EnumTypeHandler.class)
@Result(column = "price", property = "price", typeHandler =MoneyTypeHandler.class)
2.在Spring中配置并使用Mybatis
在实际使⽤时,我们只需要在 Spring Framework 的上下⽂中配置⼏个 Bean 就可以 了。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 按需定义Mapper Bean -->
<bean id="menuItemMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="learning.spring.binarytea.repository.MenuItemMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
在Mapper多的时候,可以直接通过扫描来实现Mapper的自动注册:
<mybatis:scan base-package="learning.spring.binarytea.repository" />
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 其他Bean配置 -->
还可以使用注解的方式:
@MapperScan("learning.spring.binarytea.repository")
public class Config {}
3. 提升MyBatis的开发效率
(1)Mybatis映射生成工具
MyBatis 官⽅提供了⼀套⽣成⼯具—— MyBatis Generator。这套工具可以根据数据库的元数据和配置文件,为我们生成如下内容:
- 与数据表对应的POJO类。
- Mapper接口,如果用注解或混合方式配置映射,接口会有对应的注解。
- SQLMap映射文件XML文件(仅在XML方式或混合方式时生成)
(2)Mybatis分页插件
PageHelper
(3)MyBatis Plus
第七章 数据访问进阶
一、连接池的实用配置
1.保护敏感的连接配置
(1)结合HikariCP与Jasypt实现密码加密
Jasypt提供了一个EncryptablePropertySoruce,可以直接解密属性值中用ENC()括起来的密文。
(2)使用Druid内置功能实现密码加密
Druid内置了数据库密码的加密功能,使用RSA非对称算法来加解密。
2.记录SQL语句执行情况
(1)结合HikariCP与P6SPY实现SQL记录
HikariCP本身并没有提供SQL日志功能,要借助P6SPY来记录执行的SQL。P6SPY是一套可以无缝拦截并记录SQL执行情况的框架,它工作在JDBC层面,所以无论我们使用什么连接池,是否使用ORM框架,都能通过P6SPY来进行拦截。
(2)使用Druid内置功能实现SQL记录
Druid内置了详尽的日志与统计功能,与密码加密功能一样,这些功能也通过Filter来实现的。
3.Druid的Filter扩展
Druid 的 Filter 是个⾮常有⽤的机制,可以拦截 DruidDataSource、 Connection、Statement、PreparedStatement、CallableStatement、 ResultSet、ResultSetMetaData、Wrapper 和 Clob 上⽅法的执⾏。这其中使 ⽤了责任链模式,也就是将不同的过滤器串联在⼀起,以实现不同的功能。 前⽂提到的数据库密码加密、数据库执⾏⽇志都是 Filter 的例⼦,在 Druid ⾥还有 ⼀个⾮常有⽤的 Filter,那就是 SQL 注⼊防⽕墙,即 WallFilter,别名是 wall。它能够有效地控制通过 Druid 执⾏的 SQL,避免恶意⾏为。
二、在Spring工程中访问Redis
1.配置Redis连接
Java主流的Redis客户端:
IO方式 | 线程安全 | API | |
---|---|---|---|
Jedis | 阻塞 | 否 | 较底层,与Redis命令对应 |
Letuce | 非阻塞 | 是 | 有较高抽象 |
Redission | 非阻塞 | 是 | 有较高抽象 |
三、Spring的缓存抽象
第八章 Spring MVC
一、Spring MVC
1.Spring MVC概览
MVC即Model-View-Controller。模型层封装了业务逻辑,视图层则是暴露给用户的界面,控制器层则在两者之间充当粘合剂。
Spring MVC的设计是围绕DispatchServlet展开的,它是整个Spring MVC的核心,跟它配合的组件主要有下面这些:
- 控制器,我们编写的各种 Controller;
- 各类校验器,例如,Spring MVC 内置了对 Hibernate Validator 的⽀持;
- 各类解析器,例如,视图解析器 ViewResolver、异常解析器 HandlerExceptionResolver 和 Multipart 解析器 MultipartResolver;
- 处理器映射,HandlerMapping 定义了请求该如何找到对应的处理器,例如, 根据 Bean 名称的 BeanNameUrlHandlerMapping,以及根据 @RequestMapping 注解的 RequestMappingHandlerMapping;
- 处理器适配器,DispatcherServlet 在收到请求时,通过 HandlerAdapter 来调⽤被映射的处理器。
二、Spring MVC的请求处理逻辑
1.请求的处理流程
(1)DispatcherServlet的初始化
(2)请求的处理过程
(3)配置SpringMVC
2.请求处理方法
(1)方法的定义
添加了@RequestMapping的方法就是用来处理HTTP请求的,所以可以通过方法的参数获得请求的各种信息,可以是原始的,也可以是经过处理的。
![](https://img-blog.csdnimg.cn/3c2d75618f964bd89c7032b9e7e349de.png)
(2)请求内容的转换
@RequestBody和@RequestPart也会对请求内容进行转换,将它们转换为各种不同的类型传到参数中,这里的转换其实就是由HttpMessageConverter来实现的。
(3)绑定与校验
在SpringMVC中可以通过带有@initBinder注解的方法来初始化WebDataBinder,它能将请求参数(表单或查询数据)绑定到模型对象上;将请求中的字符串转换为控制器方法中的各种参数类型;在呈现HTML表单时,将模型对象中的值格式化为字符串。