文章目录
- Spring篇
- Spring核心
- 推断构造方法
- AOP
- 动态代理
- Advice的分类
- Advisor的理解
- AOP相关的概念
- 定义Bean
- ASM技术
- JFR
- 依赖注入
- 循环依赖
- Lifecycle
- Spring AOT
- Spring事务
- Spring事务传播机制
- Spring事务传播机制是如何实现的呢?
- Spring事务传播机制分类
- SpringMVC
- Handler
- HandlerMapping
- HandlerAdapter
- @RequestMapping方法参数
- SPI
- Spring MVC不常用注解
- @InitBinder
- @SessionAttributes
- @RequestAttribute 与 @SessionAttribute
- @ModelAttribute
- flashMap
- MyBatis篇
- MyBatis执行SQL流程分析
- MyBatis重要类分析
- MyBatis的二级缓存原理
- OGNL表达式
- Mybatis中的OGNL表达式
- Mybatis中的OGNL表达式拓展
Spring篇
Spring核心
推断构造方法
Spring会根据入参的类型和入参的名字去Spring中找Bean对象(以单例Bean为例,Spring会从单例池那个Map中去找):
- 先根据入参类型找,如果只找到一个,那就直接用来作为入参
- 如果根据类型找到多个,则再根据入参名字来确定唯一一个,会根据入参名字来找Map里面一样名字的作为入参。
- 最终如果没有找到,则会报错,无法创建当前Bean对象
确定用哪个构造方法,确定入参的Bean对象,这个过程就叫做推断构造方法。
AOP
AOP全称是 Aspect Oriented Programming 即:面向切面编程。是OOP的延续,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型。简单的说他就是把我们程序重复的代码抽取出来,在需要执行的时候使用动态代理技术在不修改源码的基础上,对我们的已有方法进行增强。
动态代理
代理模式的解释:为其他对象提供一种代理以控制对这个对象的访问,增强一个类中的某个方法,对程序进行扩展。
在Spring中进行了封装,封装出来的类叫做ProxyFactory,表示是创建代理对象的一个工厂,使用起来会比JDK动态代理、cglib动态代理更加方便。
UserService target = new UserService();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before...");
Object result = invocation.proceed();
System.out.println("after...");
return result;
}
});
UserInterface userService = (UserInterface) proxyFactory.getProxy();
userService.test();
通过ProxyFactory,我们可以不再关心到底是用cglib还是jdk动态代理了,ProxyFactory会帮我们去判断,如果UserService实现了接口,那么ProxyFactory底层就会用jdk动态代理,如果没有实现接口,就会用cglib技术,上面的代码,就是由于UserService实现了UserInterface接口,所以最后产生的代理对象是UserInterface类型。
Advice的分类
- Before Advice:方法之前执行
- After returning advice:方法return后执行
- After throwing advice:方法抛异常后执行
- After (finally) advice:方法执行完finally之后执行,这是最后的,比return更后
- Around advice:这是功能最强大的Advice,可以自定义执行顺序
Advisor的理解
跟Advice类似的还有一个Advisor的概念,一个Advisor是有一个Pointcut和一个Advice组成的,通过Pointcut可以指定要需要被代理的逻辑,比如一个UserService类中有两个方法,按上面的例子,这两个方法都会被代理,被增强,那么我们现在可以通过Advisor,来控制到具体代理哪一个方法,比如:
UserService target = new UserService();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(new PointcutAdvisor() {
@Override
public Pointcut getPointcut() {
return new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().equals("testAbc");
}
};
}
@Override
public Advice getAdvice() {
return new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before...");
Object result = invocation.proceed();
System.out.println("after...");
return result;
}
};
}
@Override
public boolean isPerInstance() {
return false;
}
});
UserInterface userService = (UserInterface) proxyFactory.getProxy();
userService.test();
上面代码表示,产生的代理对象,只有在执行testAbc这个方法时才会被增强,会执行额外的逻辑,而在执行其他方法时是不会增强的。
AOP相关的概念
- Aspect:表示切面,比如被@Aspect注解的类就是切面,可以在切面中去定义Pointcut、Advice等等
- Join point:表示连接点,表示一个程序在执行过程中的一个点,比如一个方法的执行,比如一个异常的处理,在Spring AOP中,一个连接点通常表示一个方法的执行。
- Advice:表示通知,表示在一个特定连接点上所采取的动作。Advice分为不同的类型,后面详细讨论,在很多AOP框架中,包括Spring,会用Interceptor拦截器来实现Advice,并且在连接点周围维护一个Interceptor链
- Pointcut:表示切点,用来匹配一个或多个连接点,Advice与切点表达式是关联在一起的,Advice将会执行在和切点表达式所匹配的连接点上
- Introduction:可以使用@DeclareParents来给所匹配的类添加一个接口,并指定一个默认实现
- Target object:目标对象,被代理对象
- AOP proxy:表示代理工厂,用来创建代理对象的,在Spring Framework中,要么是JDK动态代理,要么是CGLIB代理
- Weaving:表示织入,表示创建代理对象的动作,这个动作可以发生在编译时期(比如Aspejctj),或者运行时,比如Spring AOP
定义Bean
Spring中定义Bean的方式可以分为两类,分别是声明式和编程式。顾名思义,声明式可以理解为用一个的标记去标识相关的信息,代码底层再对这些标识符进行一个解析处理;编程式则更多侧重于使用代码的形式去处理相关逻辑。前者使用更友好,后者更底层。
声明式:标签、@Bean注解、@Component注解
编程式:BeanDefinition接口、FactoryBean接口、Supplier接口
ASM技术
在Spring中需要去解析类的信息,比如类名、类中的方法、类上的注解,这些都可以称之为类的元数据,所以Spring中对类的元数据做了抽象,并提供了一些工具类。MetadataReader表示类的元数据读取器,默认实现类为SimpleMetadataReader。需要注意的是,SimpleMetadataReader去解析类时,使用的ASM技术。
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。ASM并没有让这个类加载到JVM。
JFR
JFR资料介绍:JFR 是 Java Flight Record (Java飞行记录) 的缩写,是 JVM 内置的基于事件的JDK监控记录框架。这个起名就是参考了黑匣子对于飞机的作用,将Java进程比喻成飞机飞行。顾名思义,这个记录主要用于问题定位和持续监控。
依赖注入
依赖注入(Dependency Injection, DI)是一种设计模式,也是Spring框架的核心概念之一。其作用是去除Java类之间的依赖关系,实现松耦合,以便于开发测试。依赖注入分为手动注入和自动注入两种注入方式。
手动注入:在XML中定义Bean时,就是手动注入,因为是程序员手动给某个属性指定了值。手动注入分为两种:
-
set方法注入
<bean name="userService" class="com.luban.service.UserService"> <property name="orderService" ref="orderService"/> </bean>
-
构造方法注入
<bean name="userService" class="com.luban.service.UserService"> <constructor-arg index="0" ref="orderService"/> </bean>
自动注入:分为两种:
-
XML的autowire自动注入
在XML中,我们可以在定义一个Bean时去指定这个Bean的自动注入模式:
- byType
- byName
- constructor
- default
- no
<bean id="userService" class="com.luban.service.UserService" autowire="byType"/>
-
@Autowired注解的自动注入
@Autowired注解可以写在:
- 属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个
- 构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个
- set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个
循环依赖
简单来说就是A对象依赖了B对象,B对象依赖了A对象。
如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。但是,在Spring中循环依赖就是一个问题了,因为在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题。
Spring解决循环依赖使用了三级缓存。三级缓存是通用的叫法。
一级缓存为:singletonObjects
二级缓存为:earlySingletonObjects
三级缓存为**:singletonFactories**
-
singletonObjects中缓存的是已经经历了完整生命周期的bean对象。
-
earlySingletonObjects比singletonObjects多了一个early,表示缓存的是早期的bean对象。早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects。
-
singletonFactories中缓存的是ObjectFactory,表示对象工厂,表示用来创建早期bean对象的工厂。
Lifecycle
Lifecycle表示的是ApplicationContext的生命周期,可以定义一个SmartLifecycle来监听ApplicationContext的启动和关闭
@Component
public class isPaintingLifecycle implements SmartLifecycle {
private boolean isRunning = false;
@Override
public void start() {
System.out.println("启动");
isRunning = true;
}
@Override
public void stop() {
// 要触发stop(),要调用context.close(),或者注册关闭钩子(context.registerShutdownHook();)
System.out.println("停止");
isRunning = false;
}
@Override
public boolean isRunning() {
return isRunning;
}
}
Spring AOT
Spring 6 提供了一项新功能,有望优化应用程序的性能: Ahead-of-Time(AOT) 编译支持。
Ahead-of-Time(AOT,提前编译或预编译)是一种在应用程序运行前将字节码预编译为本地机器码的技术。
通过 AOT 编译构建的应用程序在性能和资源消耗方面具有多重优势:
- 消除死代码:AOT 编译器可以消除运行时从未执行过的代码。这样可以减少需要执行的代码量,从而提高性能。
- 内联:内联是 AOT 编译器用函数的实际代码,替换函数调用的一种技术。这可以减少函数调用的开销,从而提高性能。
- 常量传播:AOT 编译器通过在编译时确定变量的常量值来替换变量,从而优化性能。这样就无需进行运行时计算,从而提高了性能。
- 过程间优化:AOT 编译器可通过分析程序的调用图来优化跨多个函数的代码。这可以通过减少函数调用的开销和识别常见的子表达式来提高性能。
- Bean 定义:Spring 6 中的 AOT 编译器可减少不必要的
BeanDefinition
实例,从而提高应用程序的效率。
因此,让我们使用 AOT 优化命令来构建应用程序:
mvn clean compile spring-boot:process-aot package
然后使用命令运行应用程序:
java -Dspring.aot.enabled=true -jar <jar-name>
我们可以设置构建插件,默认启用 AOT 编译:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>process-aot</id>
<goals>
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>
Spring事务
Spring事务传播机制
Spring事务传播机制是如何实现的呢?
在执行某个方法时,判断当前是否已经存在一个事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象,如果存在则表示已经存在一个事务了。
Spring事务传播机制分类
REQUIRED (默认传播行为),支持当前事务,如果当前没有事务,就新建一个事务,这个当前事务指的是上一个方法的事务,是别人传递过去的,类似于重入锁,A方法和B方法都有事务,A方法调用B方法,A的事务会传递给B,使它们共用同一个事务,我起了个名字叫做重入事务
SUPPORTS 如果存在一个事务,支持当前事务,如果没有事务,则非事务执行,
REQUIRES_NEW 开启一个新的事务。如果一个事务已经存在,则先将这个存在的事务挂起
MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常
NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务
NEVER 总是非事务地执行,不加入任何事务;
NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按 REQUIRED 属性执行。
SpringMVC
Handler
Handler表示请求处理器,在SpringMVC中有四种Handler:
1、实现了Controller接口的Bean对象
2、实现了HttpRequestHandler接口的Bean对象
3、添加了@RequestMapping注解的方法
4、一个HandlerFunction对象
HandlerMapping
HandlerMapping负责去寻找Handler,并且保存路径和Handler之间的映射关系。
因为有不同类型的Handler,所以在SpringMVC中会由不同的HandlerMapping来负责寻找Handler
HandlerAdapter
由于有不同种类的Handler,所以执行方式是不一样的。所以,按逻辑来说,找到Handler之后,我们得判断它的类型。把不同种类的Handler适配成一个HandlerAdapter,后续再执行HandlerAdapter的handle()方法就能执行不同种类Hanlder对应的方法。
针对不同的Handler,会有不同的适配器。
@RequestMapping方法参数
当SpringMVC接收到请求,并找到了对应的Method之后,就要执行该方法了,不过在执行之前需要根据方法定义的参数信息,从请求中获取出对应的数据,然后将数据传给方法并执行。
一个HttpServletRequest通常有:
1、request parameter
2、request attribute
3、request session
4、reqeust header
5、reqeust body
比如如下几个方法:
public String test(String username) {
return "ispainting";
}
表示要从request parameter中获取key为username的value
public String test(@RequestParam("uname") String username) {
return "ispainting";
}
表示要从request parameter中获取key为uname的value
public String test(@RequestAttribute String username) {
return "ispainting";
}
表示要从request attribute中获取key为username的value
public String test(@SessionAttribute String username) {
return "ispainting";
}
表示要从request session中获取key为username的value
public String test(@RequestHeader String username) {
return "ispainting";
}
表示要从request header中获取key为username的value
public String test(@RequestBody String username) {
return "ispainting";
}
表示获取整个请求体
SPI
跟Tomcat的提供的扩展机制有关,在SpringMVC中有这样一个类:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
// ...
}
}
这个类实现了javax.servlet.ServletContainerInitializer接口,并且在SpringMVC中还有这样一个文件:META-INF/services/Tomcatjavax.servlet.ServletContainerInitializer,文件内容为org.springframework.web.SpringServletContainerInitializer。
很明显,是SPI,所以Tomcat在启动过程中会找到这个SpringServletContainerInitializer,并执行onStartup()
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件。Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。
SPI与API区别:
- API是调用并用于实现目标的类、接口、方法等的描述;
- SPI是扩展和实现以实现目标的类、接口、方法等的描述;
Spring MVC不常用注解
@InitBinder
在参数绑定时进行可以针对复杂对象自定义参数绑定逻辑,比如:
@GetMapping("/testInitBinder")
public String testInitBinder(Date date){
return date.toString();
}
此时如果访问:http://localhost:8080/tuling-web/app/testInitBinder?date=1111-1-1,会报错。
此时可以在当前Controller中添加:
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
有了这个就相当于添加了一个自定义的日期格式转换器,这样就能正常访问了。
注意,如果我们想把String类型转成我们自定义的User类,那么在User类中得提供一个String类型的构造方法
@SessionAttributes
只能写在类上,通过@SessionAttributes注解指定model中哪些key的value存到session中。
@RequestAttribute 与 @SessionAttribute
都只能写在方法参数前面,表示从相对应request.getAttribute()、session.getAttribute()中获取值传递给方法参数
@ModelAttribute
可以写在某个方法上,@ModelAttribute可以定义在一个Controller中,当请求这个Controller中的方法时,会先调用@ModelAttribute所修饰的方法,方法返回的值会添加到model中,比如以下代码就会向model中添加一个key为user,values为请求中user参数所传递的值。
flashMap
在重定向时,FlashMap机制提供了这么一种方式让一个请求传递一些参数给接下来的某个请求,FlashMap机制保证了参数的隐蔽性,不需要将参数传递到前端。
我们可以通过FlashMap来进行传递:
@Controller
public class IsPaintingController {
@GetMapping("/a")
public String test(HttpServletRequest request, Model model) {
FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(request);
outputFlashMap.put("username", "ispainting");
return "redirect:/b";
}
@GetMapping("/b")
@ResponseBody
public String a(HttpServletRequest request, Model model) {
Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
String username = (String) inputFlashMap.get("username");
return username;
}
}
把要传递的参数存入outputFlashMap,在b请求里通过inputFlashMap就可以拿到了,底层是基于session来实现的。
MyBatis篇
ORM是"Object-Relational Mapping"的缩写,中文通常翻译为"对象关系映射"。
传统JDBC规范(掌握四个核心对象):
DriverManager:用于注册驱动
Connection: 表示与数据库创建的连接
Statement: 操作数据库sql语句的对象
ResultSet: 结果集或一张虚拟表
MyBatis执行SQL流程分析
Executor分成两大类,一类是CacheExecutor,另一类是普通Executor。
CacheExecutor其实是封装了普通的Executor,和普通的区别是在查询前先会查询缓存中是否存在结果,如果存在就使用缓存中的结果,如果不存在还是使用普通的Executor进行查询,再将查询出来的结果存入缓存。
普通Executor又分为三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
- SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
- ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
- BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
MyBatis重要类分析
重要类
- MapperRegistry:本质上是一个Map,其中的key是Mapper接口的全限定名,value的MapperProxyFactory;
- MapperProxyFactory:这个类是MapperRegistry中存的value值,在通过sqlSession获取Mapper时,其实先获取到的是这个工厂,然后通过这个工厂创建Mapper的动态代理类;
- MapperProxy:实现了InvocationHandler接口,Mapper的动态代理接口方法的调用都会到达这个类的invoke方法;
- MapperMethod:判断你当前执行的方式是增删改查哪一种,并通过SqlSession执行相应的操作;
- SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
- Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;
StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数,
ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement:MappedStatement维护了一条节点的封装,
SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql:表示动态生成的SQL语句以及相应的参数信息
Configuration:MyBatis所有的配置信息都维持在Configuration对象之中。
MyBatis的二级缓存原理
mybatis使用的是溢出淘汰机制。二级缓存在结构设计上采用装饰器+责任链模式
mybatis缓存分为一级缓存和二级缓存
- 一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),而执行器又在SqlSession(DefaultSqlSession)中,所以一级缓存的生命周期与SqlSession是相同的。
- 二级缓存,又叫自定义缓存,实现了Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中。
二级缓存对象的默认类型为PerpetualCache,如果配置的缓存是默认类型,则mybatis会根据配置自动追加一系列装饰器。
OGNL表达式
OGNL是一种表达式语言,专门用于处理对象图中的数据。
OGNL表达式的语法非常简洁明了,类似于XPath表达式。它使用点符号(.)来访问对象的属性,使用方括号([])来访问集合和数组元素。以下是一些常见的OGNL语法示例:
-
访问属性:假设有一个名为person的对象,其中包含属性name和age,我们可以使用如下OGNL表达式来获取属性值:
person.nameperson.age
-
调用方法:假设有一个名为order的对象,其中包含方法getTotal(),我们可以使用如下OGNL表达式来调用该方法
order.getTotal()
-
访问集合和数组:假设有一个名为items的List对象,我们可以使用如下OGNL表达式来访问其中的元素:
items[0]items[1]
Mybatis中的OGNL表达式
在Mybatis中,可以在SQL映射文件中使用${和}符号来嵌入OGNL表达式,如:
<select id="findPersonById" resultType="Person">
SELECT * FROM person WHERE id = #{id} AND city = #{address.city}
</select>
其中,#{id}和#{address.city}是OGNL表达式,用于访问Person对象的id属性和Address对象的city属性。
Mybatis中的OGNL表达式拓展
在Mybatis中,可以使用Hutool的OGNL工具类来实现OGNL表达式的拓展。
<select id="findPersons" resultType="Person">
SELECT * FROM person
<if test="@cn.hutool.core.util.StrUtil@isNotBlank(name)">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="@cn.hutool.core.util.StrUtil@isNotBlank(city)">
AND city = #{city.substring(0, 2)}
</if>
</select>
其中,@cn.hutool.core.util.StrUtil@isNotBlank(name)和@cn.hutool.core.util.StrUtil@isNotBlank(city)是OGNL表达式,用于调用StrUtil工具类的isNotBlank方法,并将name和city属性作为参数传递。CONCAT(‘%’, #{name}, ‘%’)是SQL函数,用于实现字符串的拼接。#{city.substring(0, 2)}是OGNL表达式,用于实现字符串的截取。
需要注意的是,在使用Hutool的OGNL工具类时,需要在Mybatis的配置文件中进行相应的配置,如:
<configuration>
<settings>
<setting name="ognl.classResolver" value="cn.hutool.ognl.HutoolClassResolver"/>
</settings>
</configuration>
其中,ognl.classResolver是Mybatis的配置项,用于指定OGNL表达式的类加载器。cn.hutool.ognl.HutoolClassResolver是Hutool的OGNL类加载器,用于实现OGNL表达式的拓展。