Spring学习笔记

摘要

 本文依照图灵教育出版的《学透Spring》记录了学习Spring所需要的难重点。

第一章 IoC容器

一、IoC容器基础

1.控制反转

控制反转是一种决定容器如何装配组件的模式。只要遵循这种模式,按照一定的规则,容器就能将组件组装起来。这里所谓的容器就是用来创建组件并对它们进行管理的地方。

2.IoC容器

IoC容器即所有的组件都要被动接受容器的控制。

Spring IoC​容器​​​​​

 3.容器的初始化

  1. 从XML文件、Java类或其他地方加载配置元数据。
  2. 通过BeanFactoryPostProcessor对配置元素进行一轮处理。
  3. 初始化Bean实例,并根据给定的依赖关系组装对象。
  4. 通过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的实现类。

常见的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.容器继承关系

子容器可以继承父容器中配置的组件。

容器继承vsjava继承
容器继承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>
<constructor-arg/>的可配置属性
属性作用
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"/>
……
Bean创建相关注解
注解说明
@Component将类标识为普通的组件,即一个Bean
@Service将类标识为服务层的服务
@Repository将类标识为数据层的数据仓库,一般是DAO(Data Access Object)
@Controller将类标识为Web层的Web控制器(后来针对REST服务又增加 @RestController注解)

可以选择指定Bean名称或者Spring容器会自动生成一个。

@Component("hello")
public class Helloe(){...}
可注入依赖的注解
注解说明
@Autowired根据类型注入依赖,可用于构造方法,Setter方法和成员变量
@ResourceJSR-250的注解,根据名称注入依赖
@InjectJSR-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。

@Bean注解的属性
属性默认值说明
name{}Bean的名称,默认同方法名
value{}同name
autowireAutowire.NO自动织入方式
autowireCandidatetrue是否是自动织入的候选Bean
initMethond""初始化方法名
destroyMethondAbstractBeanDefinition.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中获取容器信息,可以通过如下两种方式:

  1. 实现BeanFactoryAware或ApplicationContextAware接口;
  2. 用@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很好的将二者融合在了一起。

AOP中的几个重要概念
概念说明
切面(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{...}

注意两点内容:

  1. 添加@Aspect注解只是告诉Spring“这个是切面”,但并没有把它声明为Bean,因此需要手动进行配置,例如添加@Component注解,或者Java配置类中进行声明。
  2. 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(pointcut designator,切入点标识符)
PCD说明
execution最常用的一个PCD,用来匹配特定方法的执行
within匹配特定范围内的类型,可以用通配符来匹配某个Java包内的所有类

this

Spring AOP代理对象这个Bean本身要匹配某个给定的类型
target目标对象要匹配某个给定的类型,比this更常用一些
args传入的方法参数要匹配某个给定的类型,它也可以用于绑定请求参数
beanSpring AOP特有的一个PCD,匹配Bean的ID或名称,可以用通配符
execution([修饰符] <返回类型> [全限定类名.]<方法>(<参数> [异常])

其中,

  1. 每个部分都可以使用*通配符
  2. 类名中使用.*表示包中的所有类,..*表示当前包与子包中的所有类
  3. 参数主要分为以下几种情况:
    • ()表示方法无参数
    • (..)表示有任意个参数
    • (*)表示有一个任意类型的参数
    • (String)表示有一个String类型的参数
    • (String, String)代表有两个String类型的参数
针对注解的常用PCD
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.内置的起步依赖

图 一些常用的SpringBoot起步依赖

 起步依赖背后使用的其实就是Maven的传递依赖机制。POM文件分为如下三个部分:

  • 起步依赖本身的描述信息
  • 导入依赖管理项
  • 具体依赖项

4.自动配置

自动配置类其实就是添加了@Configuration的普通java配置类,他利用Spring Framework 4.0加入的条件注解@Conditional来实现“根据特定条件启用相关配置类”,注解中传入的Condition类就是不同条件的判断逻辑。

配置项加载机制

(1)SpringBoot的属性加载优先级

SpringBoot有18种方式来加载属性。

(2)SpringBoot的配置文件

SpringBoot会按照如下优先级加载属性:

  1. 打包后的Jar包以外的application-{profile}.properties
  2. 打包后的Jar包以外的application.properties
  3. Jar包内部的application-{profile}.properties
  4. Jar包内部的application.properties

(3)类型安全的配置属性

通常,我们会在类中用@Value("{}")注解来访问属性,或者在XML文件中使用${}占位符。在配置中,可能会有大量的属性需要一一对应到类的成员变量上,SpringBoot提供了一种结构化且类型安全的方式来处理配置属性(configuration properties)——使用@ConfigurationProperties注解。

5.自己的自动配置配置和起步依赖

(1)编写自己的自动配置

编写自己的自动配置只需要三个步骤:

  1. 编写常规的配置类
  2. 为配置类添加条件与顺序
  3. 在/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 Boot 提供的部分常⽤ spring.datasource 配置项
配置项说明
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.统一的异常抽象

图 统⼀数据库操作异常抽象中部分常⽤的异常类

 2.自定义错误码处理逻辑

第六章 对象给关系映射

一、通过Hibernate操作数据库

新的SSH为Spring Framework、Spring MVC与Hibernate。

二、通过MyBatis操作数据库

1.定义Mybatis映射

(1)通过注解定义常用操作映射

Mybatis中常用的注解
注解作用重点属性说明
@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客户端:

主流的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请求的,所以可以通过方法的参数获得请求的各种信息,可以是原始的,也可以是经过处理的。

图 SpringMVC请求处理方法的常见返回值类型

 (2)请求内容的转换

@RequestBody和@RequestPart也会对请求内容进行转换,将它们转换为各种不同的类型传到参数中,这里的转换其实就是由HttpMessageConverter来实现的。

(3)绑定与校验

在SpringMVC中可以通过带有@initBinder注解的方法来初始化WebDataBinder,它能将请求参数(表单或查询数据)绑定到模型对象上;将请求中的字符串转换为控制器方法中的各种参数类型;在呈现HTML表单时,将模型对象中的值格式化为字符串。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值