简介

本文主要整理一些Spring & SpringBoot应用时和相关原理的知识点,对于源码不做没有深入的讲解。

1. 思维导图

右键新窗口打开可以放大。

说明

  • 使用@Configuration在java代码中声明一个bean——而不是使用xml——实际上很早就有了(至少在《Spring实战(第3版)》出版时,也就是Spring3.0时),我一直以为是SpringBoot的新特性。

2. Spring

2.1 AOP术语

  • 通知Advice —— 切面要做什么,何时执行。何时,包括方法调用前、方法调用后、方法成功调用后、方法调用抛异常后、环绕(Around)。环绕允许提供一些需要跨越方法调用前后的功能,如计算调用耗时。
  • 连接点Joinpoint —— 应用执行时能插入切面的点。实际上是一个逻辑概念,不体现在配置中。
  • 切点Pointcut —— 执行通知的具体的连接点。
  • 切面Aspect —— 通知+切点
  • 引入Introduction —— 允许为类添加新的方法或属性。(个人理解即应用使用切面中的方法和属性,就好像这些方法和属性是原生的一样。可以参考<aop:declare-parents>元素)
  • 织入Weaving —— 将切面应用到目标对象创建新的代理对象的过程,Spring使用的是运行时。编译期和类加载时是其他的方式。

2.2 Bean的生命周期

虽然被称为生命周期,实际上指的是bean在初始化、回收期间调用了哪些方法。如果只看《Spring实战》,可以看到类似下面的图(图源:参考文献[3])



具体哪一步做了什么?其实这些方法都是可选的,自定义的bean可以实现,也可以不实现,实现里写什么似乎都没问题,参考文献[3]中的测试代码展示了这些方法在bean生命周期中的执行顺序。

但是对于Spring框架的bean,就有必要的用途了。这里没有深入研究,有兴趣可以自行读源码。

2.3 Cglib和JdkProxy

2.3.1 与Spring的关系

这是Spring AOP的两种实现方式。根据官方文档:

  1. 默认使用JdkProxy
  2. 对于被代理对象没有实现任何接口,使用Cglib
  3. 可以强制指定使用Cglib。

    这样就可以解释为什么有的bean实现了接口,有的没有,但是在同一个工程中可以并存了。

2.3.2 示例代码

本节代码改写自参考文献[5]。

//用户管理接口
public interface UserManager {
//新增用户抽象方法
void addUser(String userName,String password);
//删除用户抽象方法
void delUser(String userName);
}
//用户管理实现类,实现用户管理接口
public class UserManagerImpl implements UserManager{
@Override
public void addUser(String userName) {
System.out.println("调用了新增的方法!");
System.out.println("传入参数为 userName: "+userName+" password: "+password);
}
@Override
public void delUser(String userName) {
System.out.println("调用了删除的方法!");
System.out.println("传入参数为 userName: "+userName);
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl;
//JDK动态代理实现InvocationHandler接口
public class JdkProxy implements InvocationHandler {
private Object target ;//需要代理的目标对象 @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理,监听开始!");
Object result = method.invoke(target, args);
System.out.println("JDK动态代理,监听结束!");
return result;
}
//定义获取代理对象方法
// 因为只是在main()里测试,声明为private了
private Object getJDKProxy(Object targetObject){
this.target = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
} public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());//获取代理对象
user.addUser("admin");
}
}
import java.lang.reflect.Method;

import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; //Cglib动态代理,实现MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
private Object target;//需要代理的目标对象 //重写拦截方法
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
System.out.println("Cglib动态代理,监听开始!");
Object invoke = method.invoke(target, arr);//方法执行,参数:target 目标对象 arr参数数组
System.out.println("Cglib动态代理,监听结束!");
return invoke;
}
//定义获取代理对象方法
public Object getCglibProxy(Object objectTarget){
//为目标对象target赋值
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
//设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);// 设置回调
Object result = enhancer.create();//创建并返回代理对象
return result;
} public static void main(String[] args) {
CglibProxy cglib = new CglibProxy();
UserManager user = (UserManager) cglib.getCglibProxy(new UserManagerImpl());
user.delUser("admin");
} }

2.3.3 比较

比较一下两者的区别,这也是常见的面试问题。

JdkProxy Cglib
依赖 被代理对象实现了接口(所有接口的方法数总和必须>0[4]) 引入asm、cglib jar ;不能是final类和方法
原理 反射,实现被代理对象接口的匿名内部类,通过InvocationHandler.invoke()包围被代理对象的方法 引入asm、cglib jar,代理类实现MethodInterceptor,通过底层重写字节码来实现
效率 创建快,运行慢(见下方说明2) 创建慢,运行快

说明

  1. Cglib是如何修改字节码,从代码上是看不出来的。使用的是ASM技术,修改class文件,可以自行查阅。
  2. JDK1.8及以后,JdkProxy的运行速度已经比Cglib快了(之前则是慢于Cglib),测试代码可见参考文献[6]。

2.3.4 关于org.apoalliance.intercept.MethodInterceptor

我读了一下之前所用的日志拦截器源码,发现其实现的是这节标题的接口:

class CommonInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
// 拦截器内部逻辑
result = invoication.proceed();
catch(Throwable e) {
// 异常处理
}
return result;
}
}

声明代理链

@Configuration
public class InterceptorConfig { @Bean
public CommonInterceptor serviceInterceptor() {
CommonInterceptor bean = new CommonInterceptor();
return bean;
} // 代理名称后缀为servie的实现类
@Bean
public BeanNameAutoProxyCreator servieBeanNameAutoProxyCreator() {
BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
creator.setName("*ServieImpl");
creator.setInterceptorNames("serviceInterceptor");
creator.setProxyTargetClass(true);
return creator;
}
}

查了一些资料,apoalliance包下只是aop的接口规范,不是具体的实现,不要把这里的MethodInterceptor和cglib的MethodInterceptor搞混。

2.4 构造方法注入和设值注入的区别

注:设值注入指的是通过setter注入。

之前看参考文献[7],感觉很难懂,试着改换了一种说法:

  1. 如果只设置基本类型(int、long等)的值,建议设置默认值而不是通过任何一种注入完成
  2. 构造注入不支持大部分的依赖注入。构造注入仅在创建时执行,设值注入的值在后续也可以变化。
  3. 设值注入可以支持尚未完整的被依赖的对象,构造注入则不行。可以通过构造注入决定依赖关系,因此如果依赖关系不会发生变更也可以选择依赖注入。

2.5 ApplicationContext事件

可以通过实现ApplicationEvent类和ApplicationListener接口,进行ApplicationContext的事件处理。这是标准的发送者-监听者的模型,可以用来处理业务逻辑,将代码解耦。

但是,发送和接收实际上是同步的,如果有事务,会在同一个事务内,并不能作为异步处理机制[8]

示例代码见参考文献[9]。

3. SpringBoot

注:工作中我对SpringBoot是偏应用的,研究并不是很深入。

3.1 如何快速搭建一个SpringBoot项目

见参考文献[10]。实际的过程是借助Spring Initializer这个网络应用程序来生成SpringBoot项目。

3.2 SpringBoot的关键注解

所谓核心注解,这里指的是相对Spring本身新增的一些注解,来看看它们有什么作用。

恰好这里提到的注解,都可以打在SpringBoot的启动类(不限于启动类),用下面的代码片段来进行说明。

3.2.1 代码示例

package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource; @PropertySource(value = "classpath:application.properties")
@MapperScan("com.example.demo.dal")
@SpringBootApplication(scanBasePackages = {"com.example.demo"})
@Import({DemoConfig1.class, DemoConfig2.class,})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

3.2.2 @PropertySource

从指定的文件读取变量。示例代码会从resource目录下读取application.properties变量的值。文件的格式如下,既可以用常量,也可以用变量替换,来对不同环境的值作区分。

name=value
env.name=$env.value

如何使用这个值?在要使用的地方获取即可。

@PropertySource(value = "classpath:application.properties")
class TestClass {
@Resource
private Environment environment; @Test
public void test() {
String value = environment.getProperty("name"));
}
}

3.2.2.1 与@Value配合使用

使用@Value可以把配置文件的值直接注入到成员变量中。

@PropertySource("classpath:application.properties")
public class PropertyConfig { @Value("${name}")
private String value; ...
}

3.2.2.2 通过@Import引用

3.2.1的示例代码中,如果类上没有@PropertySource,但DemoConfig1或DemoConfig2中有@PropertySource,通过@Import可以将它们加载的变量也读出来。

@Import的作用在下文会继续介绍。

3.2.2.3 .properties和.yml配置文件

@PropertySource只能导入.properties配置文件里的内容,对于.yml是不支持的。看了一些文章,得出结论是yml文件是不需要注解就能导入,但是需要路径。

Springboot有两种核心配置文件,application和bootstrap,都可以用properties或yml格式。区别在于bootstrap比application优先加载,并且不可覆盖。

3.2.3 @MapperScan

这实际上是一个mybatis注解,作用是为指定路径下的DAO接口,通过sqlmapping.xml文件,生成实现类。

3.2.4 @SpringBootApplication

@SpringBootApplication是由多个注解组合成的。源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// 略
}
简单介绍一下这些注解。 #### 3.2.4.1 元注解
最上面四行都是元注解。回忆一下它们的作用<sup>[12]</sup>:
* @Target 注解可以用在哪。TYPE表示类型,如类、接口、枚举
* @Retention 注解的保留时间期限。只有RUNTIME类型可以在运行时通过反射获取其值
* @Documented 该注解在生成javadoc文档时是否保留
* @Inherited 被注解的元素,是否具有继承性,如子类可以继承父类的注解而不必显式的写下来。 #### 3.2.4.2 @SpringBootConfiguration
标注这是一个SpringBoot的配置类,和@Configuration功能是相通的,从源码也可以看出它直接使用了@Configuration。 #### 3.2.4.3 @EnableAutoConfiguration
这个注解是实现自动化配置的核心注解,定义如下
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// 略
}

借助@Import引入的AutoConfigurationImportSelector,SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。具体的细节这里不展开了。

3.2.4.4 @ComponentScan

扫描@Service、@Repository、@Component、@Controller等标注的类,创建bean。

可以设置扫描范围,决定类哪些生成bean,哪些类不生成。

3.2.5 @Import

将外部资源(bean、@Configuration配置类)导入到当前IOC容器中。

使用@Import便可以实例化引用的jar中定义的bean了。

3.3 Starter

指的是在依赖中引用的各种starter包。starter可以看作是“依赖jar+配置”打包的结果,目的是降低开发者引入组件的成本,不用自己梳理依赖、编写配置文件。

starter遵循“约定大于配置”的原则,使用的组件的配置大部分都有默认值,不声明也可以直接用。

创建一个Spring boot的简易步骤(完整的可以看参考文献[14]):

  1. 创建maven项目
  2. 创建proterties类来保存配置信息
  3. 编写业务功能类(包含会实例化为bean的类)
  4. 编写Configuration类,定义bean
  5. 在resources 文件夹下新建目录 META-INF,在目录中新建 spring.factories 文件,并且在 spring.factories 中配置AutoConfiguration
  6. 打包

3.4 war包

3.4.1 和jar包的区别

  • jar 把类和相关的资源封装
  • war 代表了一个可部署的Web应用程序

3.4.2 SpringBoot项目打war包部署

通用步骤如下,其中1可能需要移除内嵌tomcat,2有其他形式,因为我工作时都是拿线程脚本打包的,没有实际操作过,下面步骤仅供参考。

  1. pom.xml修改为按war打包
  2. 修改main入口方法,继承一个SpringBootServletInitializer类,并且覆盖configure方法
  3. maven打包
  4. 复制到tomcat路径下(tomcat需要预先配置),使用startup启动

3.5 Springboot面试题补充

本节内容结合了参考文献[16]进行补充,上面提到的知识不再重复。

3.5.1 使用Springboot的两种方式

  1. 继承spring-boot-starter-parent项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
  1. 导入spring-boot-dependencies项目依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

3.5.2 SpringBoot 需要独立的容器运行吗?

可以不需要,内置了 Tomcat/Jetty等容器。

如何使用Jetty?排除掉Tomcat依赖并引入Jetty,并更改一些application配置。两种容器的比较和替换方式见参考文献[17]。

3.5.3 运行 SpringBoot 有哪几种方式?

  1. 打包用命令或者放到容器中运行
  2. 用 Maven/ Gradle 插件运行
  3. 直接执行 main 方法运行

3.5.4 SpringBoot启动时运行特定代码的方式

Bean实现接口 ApplicationRunner或者CommandLineRunner即可。

3.5.5 SpringBoot实现热部署有哪几种方式?

主要有两种

  • Spring Loaded —— 引用依赖(maven plugin)。对于注解和xml变动无法感知需要重启
  • Spring-boot-devtools —— 引用依赖、更改配置(可选)、启动idea的自动编译。注意生产环境插件可能导致gc

3.5.6 Spring Boot 可以兼容老 Spring 项目吗,如何做?

可以兼容,使用 @ImportResource 注解导入老 Spring 项目配置文件。

参考文献

1.AOP -连接点和切点的区别

2.Spring AOP术语:连接点和切点的区别。

3.深究Spring中Bean的生命周期

4.Spring AOP代理用的到底是CGLIB还是JDK动态代理

5. Spring的两种动态代理:Jdk和Cglib 的区别和实现

6. Spring AOP中的JDK和CGLib动态代理哪个效率更高?

7. 经典面试题-构造方法注入和设值注入有什么区别?

8. Spring的ApplicationEvent

9. spring-第三篇之ApplicationContext的事件机制

10. 使用IDEA搭建一个简单的SpringBoot项目——详细过程

11. 浅析PropertySource 基本使用

12. JAVA核心知识点--元注解详解

13. 简单讲讲@SpringBootApplication

14. Spring Boot Starters

15. SpringBoot 打包成war

16. 吐血整理 20 道 Spring Boot 面试题,我经常拿来面试别人!

17. SpringBoot2使用Jetty容器(替换默认Tomcat)

Spring和Springboot相关知识点整理的更多相关文章

  1. Spring SpringMVC SpringBoot SpringCloud 注解整理大全

    Spring SpringMVC SpringBoot SpringCloud 注解整理 才开的博客所以放了一篇以前整理的文档,如果有需要添加修改的地方欢迎指正,我会修改的φ(๑˃∀˂๑)♪ Spri ...

  2. EasyUI相关知识点整理

    EasyUI相关知识整理 EasyUI是一种基于jQuery.Angular..Vue和React的用户界面插件集合.easyui为创建现代化,互动,JavaScript应用程序,提供必要的功能.也就 ...

  3. Idea Spring 、SpringBoot相关设置技巧

    1.Spring变量依赖注入出现红色波浪线 Could not autowire. No beans of 'UserMapper' type found. less... (Ctrl+F1) Che ...

  4. Java容器相关知识点整理

    结合一些文章阅读源码后整理的Java容器常见知识点.对于一些代码细节,本文不展开来讲,有兴趣可以自行阅读参考文献. 1. 思维导图 各个容器的知识点比较分散,没有在思维导图上体现,因此看上去右半部分很 ...

  5. 二十三、Spring框架的相关知识点总结

    1.Spring的优点: 1.1.Spring在大小和透明性方面是轻量级的,Spring框架大约只有2MB大小. 1.2.控制反转(IOC):使用控制反转技术实现了低耦合,依赖注入(DI)到对象,而不 ...

  6. iframe高度相关知识点整理

    IFRAME 元素也就是文档中的文档. contentWindow属性是指指定的frame或者iframe所在的window对象. 用iframe嵌套页面是,如果父页面要获取子页面里面的内容,可以使用 ...

  7. javascript位置相关知识点整理

    1.css指定元素的位置采用的是文档坐标: 2.js查询元素位置的方法返回的是元素在视口中的位置,即视口坐标: 如何获得元素的位置和尺寸 获得元素的位置和尺寸可以通过getBoundingClient ...

  8. H5相关知识点整理

    01-HTML5基础 了解HTML5 ☞HTML5属于上一代HTML的新迭代语言,设计HTML5最主要的目的是为了在移动设备上支持多媒体!!! 例如: video 标签和 audio 及 canvas ...

  9. 回顾maven项目的spring boot相关知识点

    2021新年快乐! 在参加完研究生考试后,感觉像是放下了一个大负担,但并不能就此以为什么都结束了.反而,当我今天去看了一下之前老师带领我们班级做的一个maven项目,感觉像是第一次看到这个,十分陌生. ...

随机推荐

  1. Flutter 动画鼻祖之CustomPaint

    老孟导读:CustomPaint可以称之为动画鼻祖,它可以实现任何酷炫的动画和效果.CustomPaint本身没有动画属性,仅仅是绘制属性,一般情况下,CustomPaint会和动画控制配合使用,达到 ...

  2. 深入理解JVM(③)——之HotSpot虚拟机对象探秘

    前言 上篇文章介绍了Java虚拟机的运行时数据区域,大致明白了Java虚拟机内存模型的概况,下面就基于实用优先的原则,以最常用的虚拟机HotSpot和最常用的内存区域Java堆为例,升入探讨一下Hot ...

  3. (Java实现) N皇后问题

    n皇后问题是一个以国际象棋为背景的问题:在n×n的国际象棋棋盘上放置n个皇后,使得任何一个皇后都无法直接吃掉其他的皇后,即任意两个皇后都不能处于同一条横行.纵行或斜线上. 蛮力法思想: 解决n皇后问题 ...

  4. C++实现车轮轨迹

    标题:车轮轴迹 栋栋每天骑自行车回家需要经过一条狭长的林荫道.道路由于年久失修,变得非常不平整.虽然栋栋每次都很颠簸,但他仍把骑车经过林荫道当成一种乐趣. 由于颠簸,栋栋骑车回家的路径是一条上下起伏的 ...

  5. java实现第七届蓝桥杯愤怒小鸟

    愤怒小鸟 题目描述 X星球愤怒的小鸟喜欢撞火车! 一根平直的铁轨上两火车间相距 1000 米 两火车 (不妨称A和B) 以时速 10米/秒 相对行驶. 愤怒的小鸟从A车出发,时速50米/秒,撞向B车, ...

  6. Swift 语法总结

    1,用 var 定义变量 ,与js类似. let 用于定义常量,定义完后不能修改. var 用于定义变量,可以修改. swift可以自动识别属性类别. 2,使用 import 语句来引入任何的 Obj ...

  7. K8S-PV和PVC的实践

    一.什么是PV和PVC? PV的全称是Persistent Volume,翻译过来为持久化存储卷,是对底层的共享存储的一种抽象,PV由管理员进行创建和配置,主要含存储能力.访问模式.存储类型.回收策略 ...

  8. 基于ABP做一个简单的系统——实战篇:1.项目准备

    现阶段需要做一个小项目,体量很小,业务功能比较简单,就想到用最熟悉的.net来做,更何况现在.net core已经跨平台,也可以在linux服务器上部署.所以决定用.net core 3.1+mysq ...

  9. 游戏开发之UI管理器(跨引擎)

    使用UI管理器的目的 使用单场景与zindex结合的方式管理UI. 能够隐藏底层UI达到优化效果. 很好的组织和管理UI. 跨引擎使用. 管理器分类 根据以往经验我开发了三种类型的管理器,队列管理器, ...

  10. 温故知新-Mysql索引结构&页&聚集索引&非聚集索

    文章目录 摘要 索引 索引概述 索引优势劣势 索引结构 BTREE 结构 B+TREE 结构 页 索引分类 索引语法 索引设计原则 聚触索引 & 非聚触索引 你的鼓励也是我创作的动力 Post ...