【mybatis】【Spring】【SpringMVC】吐血整理

Mybatis

什么是Mybatis

1、Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时

只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建

statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性

能,灵活度高。

2、MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数

据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

3、通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过

java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最

后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返

回 result 的过程)。

mybatis文件标签

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.dianping.dal.CategoryModelMapper">
  <resultMap id="BaseResultMap" type="com.imooc.dianping.model.CategoryModel">

Mybatis的优点

1、基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任

何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML

标签,支持编写动态 SQL 语句,并可重用。

2、与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不

需要手动开关连接;

3、很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要

JDBC 支持的数据库 MyBatis 都支持)。

4、能够与 Spring 很好的集成;

5、提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射

标签,支持对象关系组件维护

mybatis的缺点

1、SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写

SQL 语句的功底有一定要求。

2、SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

Mybatis与Hibernate有哪些不同

1、Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要

程序员自己编写 Sql 语句。

2、Mybatis 直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高,非常

适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需

求变化要求迅速输出成果。但是灵活的前提是 mybatis 无法做到数据库无关性,

如果需要实现支持多种数据库的软件,则需要自定义多套 sql 映射文件,工作量大。

3、Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的

软件,如果用 hibernate 开发可以节省很多代码,提高效率。

#{}和${}的区别是什么?

#{}是预编译处理,${}是字符串替换。

Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的

set 方法来赋值;

Mybatis 在处理 时 , 就 是 把 {}时,就是把 {}替换成变量的值。

使用#{}可以有效的防止 SQL 注入,提高系统安全性。

当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

第 1 种: 通过在查询的 sql 语句中定义字段名的别名,让字段名的别名和实体类

的属性名一致。

第 2 种: 通过来映射字段名和实体类属性名的一一对应的关系。

模糊查询like语句怎么写

第 1 种:在 Java 代码中添加 sql 通配符。

select * from foo where bar like #{value}

第 2 种:在 sql 语句中拼接通配符,会引起 sql 注入

select * from foo where bar like “%”#{value}"%"

通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?

Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;

接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的

参数,就是传递给 sql 的参数。

Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符

串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个

、、、标签,都会被解析为一个

MapperStatement 对象。

举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯

一找到 namespace 为 com.mybatis3.mappers.StudentDao 下面 id 为

findStudentById 的 MapperStatement。

Mapper 接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻

找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK

动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而

执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。

mybatis是如何进行分页的,分页插件的原理是什么

Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页。可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。

mybatis是如何将sql执行结果封装为目标对象并返回的,都有哪些映射形式

第一种是使用标签,逐一定义数据库列名和对象属性名之间的映射关系。

第二种是使用 sql 列的别名功能,将列的别名书写为对象属性名。有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

如何执行批量插入

首先,创建一个简单的 insert 语句:

insert into names (name) values (#{value})

然后在 java 代码中像下面这样执行批处理插入:

try {

namemapper mapper = sqlsession.getmapper(namemapper.class);

for (string name: names) {

mapper.insertname(name);

}

sqlsession.commit();

}

如何获取自动生成的主键值

insert 方法总是返回一个 int 值 ,这个值代表的是插入的行数。

如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。

在mapper中如何传递多个参数

1、第一种:

DAO 层的函数

public UserselectUser(String name,String area);对应的 xml,#{0}代表接收的是 dao 层中的第一个参数,#{1}代表 dao 层中第二参数,更多参数一致往后加即可。

<select id="selectUser"resultMap=“BaseResultMap”>

select * fromuser_user_t

whereuser_name = #{0}

anduser_area=#{1}

2、第二种: 使用 @param 注解:

public interface usermapper {

user selectuser(@param(“username”) string

username,@param(“hashedpassword”) string hashedpassword);

}

然后,就可以在 xml 像下面这样使用(推荐封装为一个 map,作为单个参数传递给mapper):

select id, username, hashedpassword

from some_table

where username = #{username}

and hashedpassword = #{hashedpassword}

3、第三种:多个参数封装成 map

try {

//映射文件的命名空间.SQL 片段的 ID,就可以调用对应的映射文件中的

SQL

//由于我们的参数超过了两个,而方法中只有一个 Object 参数收集,因此我们使用 Map 集合来装载我们的参数

Map < String, Object > map = new HashMap();

map.put(“start”, start);

map.put(“end”, end);

return sqlSession.selectList(“StudentID.pagination”, map);

} catch (Exception e) {

e.printStackTrace();

sqlSession.rollback();

throw e;

} finally {

MybatisUtil.closeSqlSession();

}

mybatis动态sql有什么用?执行原理?有哪些动态sql

Mybatis 动态 sql 可以在 Xml 映射文件内,以标签的形式编写动态 sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接 sql 的功能。

Mybatis 提供了 9 种动态 sql 标签:trim | where | set | foreach | if | choose| when | otherwise | bind。

XML文件中,除了常见的select|insert|updae|delete**标签之外,还有哪些标签?

、、、、,加上动态 sql 的 9 个标签,其中为 sql 片段标签,通过标签引入 sql 片段,为不支持自增的主键生成策略标签。

Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?

不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;

原因就是 namespace+id 是作为 Map<String, MapperStatement>的 key使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。

为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?

Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

一对一、一对多的关联查询

<select id=“getClass” parameterType=“int”

resultMap=“ClassesResultMap”>

select * from class c,teacher t where c.teacher_id=t.t_id and

c.c_id=#{id}

<association property=“teacher”

javaType=“com.lcb.user.Teacher”>

<select id=“getClass2” parameterType=“int”

resultMap=“ClassesResultMap2”>

select * from class c,teacher t,student s where c.teacher_id=t.t_id

and c.c_id=s.class_id and c.c_id=#{id}

<association property=“teacher”

javaType=“com.lcb.user.Teacher”>

<collection property=“student”

ofType=“com.lcb.user.Student”>

mybatis实现一对一有几种方式?具体怎么操作

有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap 里面配置 association 节点配置一对一的类就可以完成;

嵌套查询是先查一个表,根据这个表里面的结果的 外键 id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置。

mybatis实现一对多有几种方式?具体是怎么操作

有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap 里面的 collection 节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键 id,去再另外一个表里面查询数据,也是通过配置 collection,但另外一个表的查询通过 select 节点配置。

mybatis是否支持延迟加载?如果支持,它的实现原理是什么

Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。

当然了,不光是 Mybatis,几乎所有的包括 Hibernate,支持延迟加载的原理都是一样的。

mybatis的一级、二级缓存

1)一级缓存:

基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

2)二级缓存与一级缓存其机制相同,默认也是采用, PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;

3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

什么是mybatis的接口绑定,有哪些实现的方法

接口绑定,就是在 MyBatis 中任意定义接口,然后把接口里面的方法和 SQL 语句绑定, 我们直接调用接口方法就可以,这样比起原来了 SqlSession 提供的方法我们可以有更加灵活的选择和设置。

接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上@Select、@Update 等注解,里面包含 Sql 语句来绑定;另外一种就是通过 xml里面写 SQL 来绑定, 在这种情况下,要指定 xml 映射文件里面的 namespace 必须为接口的全路径名。当 Sql 语句比较简单时候,用注解绑定, 当 SQL 语句比较复杂时候,用 xml 绑定,一般用 xml 绑定的比较多。

使用mybatis的mapper接口调用时有哪些要求

1、Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同;

2、Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的parameterType 的类型相同;

3、Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的resultType 的类型相同;

4、Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径。

简述mybatis的插件运行原理,以及如何编写一个插件

Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

编写插件:实现 Mybatis 的 Interceptor 接口并复写 intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。

Spring

<!-- SpringMVC -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>4.3.7.RELEASE</version>
</dependency>

<!-- Spring JDBC -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>4.3.7.RELEASE</version>
</dependency>

<!-- Spring AOP -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>4.3.7.RELEASE</version>
</dependency>

我们可以围绕 是何、为何、如何 来谈:

图片

手写一个Spring-Web框架

首先我们要实现自己的框架,就需要对原版框架流程了解清晰;

  1. 用户发送请求至前端控制器DispatcherServlet;
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器;
  3. 处理器映射器根据url请求找到具体的处理器,生成处理器对象以及处理器拦截一并返回DispatchServler。
  4. DispatchServlet通过HandlerAdapter处理器适配器调用处理器;
  5. 执行处理器(Controller,也叫后端控制器)
  6. Controller执行完成返回ModelAndView
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet;
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器;
  9. ViewReslover解析后返回具体View
  10. DispatchServlet对view进行渲染视图(即将模型数据填充到视图中)
  11. DispatcherServlet响应客户
  • 导入Servlet的支持包,配置Servlet
  • 需要实现配置文件中,新建一个类类DispatcherServlet注册为Servlet,并且设置这个Servlet处理所有请求URL请求。配置controller扫描路径scanPackage
  • 定义三个注解@Controller @RequestMapping @RequestParam
  • 实现配置文件写的DispatcherServlet类,这个类需要继承HttpServlet类;这个类中要重写三个方法init()、doGet()和doPost()方法,

​ (1)init方法传入的参数ServletConfig类,就是在web.xml文件中配置的信息,Servlet启动时会自动解析xml文件封装成ServletConfig类

​ init方法首先初始化了一个 Spring 容器。其主要的功能就是读取配置 文件,接着扫描目标包下所有的 Controller,最后实例化所有的 Controller,并且绑定 URL 路由。

​ (2)doInstance()方法主要就是把上一步中包下的所有类遍历一遍,找到加上了Controller注解的类,添加到Spring容器里

​ (3)一个请求的到来,是到达doGet方法和doPost方法的。我们自己实现一个doDispatch方法来进行自定义处理。此方法先要分离出请求的URL和请求参数,找到对应的方法后通过反射调用

​ 反射调用方法传参的方式,是通过一个 Object 数组的方式传入参数的,按照方法定义参数的顺序,将值存放在数组中,在反射调用时将数组传入即可。在最后,将 request 域中获取到的参数作为方法参数存入 paramValues 数组。

​ 而doGet和doPost方法,则直接调用doDispatch

SpringIOC

进入官网,在第二章core部分,有它的文档,包含了IOC容器,AOP等等,点进去可以在what,why,how之中寻找答案。

很好的解释了大名鼎鼎的IOC控制反转

IOC:把创建和管理bean的过程转移给了第三方。这个第三方就是容器

容器:容器负责创建、配置和管理bean,也就是它管理着bean的生命,控制着bean的依赖注入。

bean:Bean其实就是包装了的Object,无论是控制反转还是依赖注入,它们的主语都是object,而bean就是由第三方包装好了的object。bean是Spring的主角

1容器

spring是如何设计容器的?

  • 使用ApplicationContext,它是BeanFactory的子类,更好的补充并实现了BeanFactory。

​ beanfactory简单粗暴,可以理解为hashmap,一般只有get和put两个功能,所以称之为低级容器;而ApplicationContext多了很多功能,因为继承了多个接口,可以称之为高级容器

  • ApplicationContext里面有两个具体实现子类,用来读取配置的。ClassPathXmlApplicationContext:从classpath中加载配置文件,更常用;FileSystemXmlApplicationContext:从本地文件中加载文件,不是很常用
  • 当我们点开classPathXmlpplicationContext时,并不是直接继承ApplicationContext的,有很多层依赖关系,每层的子类都是对父类的补充实现,最上层的class回到了BeanFactory
2深入理解IOC

SpringIOC就是通过这个set方法注入的,也可以理解为将new–转为set

ICO是用它的容器来创建、管理这些对象的,其实也就是用的这个Set方法

3何为控制,控制的是什么?

是Bean的创建、管理的权利,控制整个生命周期

4何为反转,反转了什么?

把这个权利交给了Spring容器,而不是自己去控制,就是反转。由之前的自己主动创建对象,变成了现在的被动接收别人给我们的对象的过程,这就是反转。

3IOC的实现方式

依赖注入

何为依赖,依赖的是什么?

程序运行需要依赖外部的资源,提供程序内对象的所需要的数据和资源

何为注入,注入什么?

配置文件把资源从外部注入到内部,容器加载了外部的文件,对象,数据,然后把这些资源注入给程序内的对象,维护了程序内外对象之间的依赖关系。控制反转是通过依赖注入实现的。

4为什么要用IOC

解耦,把对象之间的依赖关系转成用配置文件来管理

5使用方式

xml文件中怎么写:

  • Bean标签:告诉Spring要创建的对象
  • ID:对象的唯一标识,就像每个人的身份证一样,不可重复
  • class:bean的完全限定名,即从packagename到classname
  • property:给属性赋值,name的名称取决于set方法后面的参数

图片

ApplicationContextIoC 容器的入口,其实也就是 Spring 程序的入口, 刚才已经说过了它的两个具体的实现子类,在这里用了从 class path 中读取数据的方式;

然后第二行,就是获取具体的 bean 了。这个其实有很多方式,在使用的时候就能看到

创建默认是单例的

Spring AOP

AOP意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可用性,同时提高开发的效率。

我们知道Java是一个面向对象(OOP)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志,权限验证,事务等功能时,只能在在每个对象里引用公共行为,这样做不便于维护,而且有大量重复代码。AOP的出现弥补了OOP的这点不足。

1切面

什么是切面?

面向对象将程序抽象成多个层次的对象,每个对象负责不同的模块,这样的话各个对象分工明确,各司其职,也不互相藕合,确实有力地促进了工程开发与分工协作,但是新的问题来了,不同的模块(对象)间有时会出现公共的行为,这种公共的行为很难通过继承的方式来实现,如果用工具类的话也不利于维护,代码也显得异常繁琐。切面(AOP)的引入就是为了解决这类问题而生的,它要达到的效果是保证开发者在不修改源代码的前提下,为系统中不同的业务组件添加某些通用功能。

图片比如上面这个例子,三个 service 对象执行过程中都存在安全,事务,缓存,性能等相同行为,这些相同的行为显然应该在同一个地方管理,有人说我可以写一个统一的工具类,在这些对象的方法前/后都嵌入此工具类,那问题来了,这些行为都属于业务无关的,使用工具类嵌入的方式导致与业务代码紧藕合,很不合工程规范,代码可维护性极差!切面就是为了解决此类问题应运而生的,能做到相同功能的统一管理,对业务代码无侵入

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
  • Target(目标对象):织入 Advice 的目标对象.。
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

怎么织入:

在说解决方案前,首先我们要看下与切面相关的几个定义图片JoinPoint: 程序在执行流程中经过的一个个时间点,这个时间点可以是方法调用时,或者是执行方法中异常抛出时,也可以是属性被修改时等时机,在这些时间点上你的切面代码是可以(注意是可以但未必)被注入的

Pointcut: JoinPoints 只是切面代码可以被织入的地方,但我并不想对所有的 JoinPoint 进行织入,这就需要某些条件来筛选出那些需要被织入的 JoinPoint,Pointcut 就是通过一组规则(使用 AspectJ pointcut expression language 来描述) 来定位到匹配的 joinpoint

Advice: 代码织入(也叫增强),Pointcut 通过其规则指定了哪些 joinpoint 可以被织入,而 Advice 则指定了这些 joinpoint 被织入(或者增强)的具体时机与逻辑,是切面代码真正被执行的地方,主要有五个织入时机

  1. Before Advice: 在 JoinPoints 执行前织入
  2. After Advice: 在 JoinPoints 执行后织入(不管是否抛出异常都会织入)
  3. After returning advice: 在 JoinPoints 执行正常退出后织入(抛出异常则不会被织入)
  4. After throwing advice: 方法执行过程中抛出异常后织入
  5. Around Advice: 这是所有 Advice 中最强大的,它在 JoinPoints 前后都可织入切面代码,也可以选择是否执行原有正常的逻辑,如果不执行原有流程,它甚至可以用自己的返回值代替原有的返回值,甚至抛出异常。在这些 advice 里我们就可以写入切面代码了 综上所述,切面(Aspect)我们可以认为就是 pointcut 和 advice,pointcut 指定了哪些 joinpoint 可以被织入,而 advice 则指定了在这些 joinpoint 上的代码织入时机与逻辑

比如在餐馆里点菜,菜单有 10 个菜,这 10 个菜就是 JoinPoint,但我只点了带有萝卜名字的菜,那么带有萝卜名字这个条件就是针对 JoinPoint(10 个菜)的筛选条件,即 pointcut,最终只有胡萝卜,白萝卜这两个 JoinPoint 满足条件,然后我们就可以在吃胡萝卜前洗手(before advice),或吃胡萝卜后买单(after advice),也可以统计吃胡萝卜的时间(around advice),这些洗手,买单,统计时间的动作都是与吃萝卜这个业务动作解藕的,都是统一写在 advice 的逻辑里

假设有以上 TestService, 实现了吃萝卜,吃蘑菇,吃白菜三个方法,这三个方法都用切面织入,所以它们都是 joinpoints,但现在我只想对吃萝卜这个 joinpoints 前后织入 advice,该怎么办呢,首先当然要声明 pointcut 表达式,这个表达式表明只想织入吃萝卜这个 joinpoint,指明了之后再让 advice 应用于此 pointcut 不就完了,比如我想在吃萝卜前洗手,吃萝卜后买单,可以写出如下切面逻辑

@Aspect
@Component
public class TestAdvice {
   // 1. 定义 PointCut
   @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())")
   private void eatCarrot(){}

   // 2. 定义应用于 JoinPoint 中所有满足 PointCut 条件的 advice, 这里我们使用 around advice,在其中织入增强逻辑
   @Around("eatCarrot()")
   public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable {
       System.out.println("吃萝卜前洗手");
       //  原来的 TestServiceImpl.eatCarrot 逻辑,可视情况决定是否执行
       point.proceed();
       System.out.println("吃萝后买单");
   }
}
2代理模式

代理在生活中随处可见,比如说我要买房,我一般不会直接和卖家对接,一般会和中介打交道,中介就是代理,卖家就是目标对象,我就是调用者,代理不仅实现了目标对象的行为(帮目标对象卖房),还可以添加上自己的动作(收保证金,签合同等),

3静态代理

静态代理说白了就是在程序运行前就已经存在代理类的字节码文件,代理类和原始类的关系在运行前就已经确定。

package test.staticProxy;
*// 接口*
public interface IUserDao {
 void save();
 void find();
}
*//目标对象*
class UserDao implements IUserDao{
 @Override
 public void save() {
  System.out.println("模拟:保存用户!");
 }
 @Override
 public void find() {
  System.out.println("模拟:查询用户");
 }
}
*/**
  静态代理
     特点:
 \1. 目标对象必须要实现接口
 \2. 代理对象,要实现与目标对象一样的接口
\*/*
class UserDaoProxy implements IUserDao{
 *// 代理对象,需要维护一个目标对象*
 private IUserDao target = new UserDao();
 @Override
 public void save() {
  System.out.println("代理操作: 开启事务...");
  target.save();  *// 执行目标对象的方法*
  System.out.println("代理操作:提交事务...");
 }
 @Override
 public void find() {
  target.find();
 }
}

1、静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理。再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。

2、第一点还不是致命的,再考虑这样一种场景:如果每个委托类的每个方法都要被织入同样的逻辑,比如说我要计算前文提到的每个委托类每个方法的耗时,就要在方法开始前,开始后分别织入计算时间的代码,那就算用代理类,它的方法也有无数这种重复的计算时间的代码

4动态代理

动态代理模式:动态代理类的源码是在程序运行期间通过JVM反射等机制动态生成,代理类和委托类的关系是运行时才确定的。实例如下:

由于动态代理是程序运行后才生成的,哪个委托类需要被代理到,只要生成动态代理即可,避免了静态代理那样的硬编码,另外所有委托类实现接口的方法都会在 Proxy 的 InvocationHandler.invoke() 中执行,这样如果要统计所有方法执行时间这样相同的逻辑,可以统一在 InvocationHandler 里写, 也就避免了静态代理那样需要在所有的方法中插入同样代码的问题,代码的可维护性极大的提高了

package test.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口
public interface IUserDao {
 void save();
 void find();
}
//目标对象
class UserDao implements IUserDao{
 @Override
 public void save() {
   System.out.println("模拟: 保存用户!");
 }
 @Override
 public void find() {
   System.out.println("查询");
 }
}
/**
* 动态代理:
*    代理工厂,给多个目标对象生成代理对象!
*
*/
class ProxyFactory {
 // 接收一个目标对象
 private Object target;
 public ProxyFactory(Object target) {
   this.target = target;
 }
 // 返回对目标对象(target)代理后的对象(proxy)
 public Object getProxyInstance() {
   Object proxy = Proxy.newProxyInstance(
     target.getClass().getClassLoader(),  // 目标对象使用的类加载器
     target.getClass().getInterfaces(),   // 目标对象实现的所有接口
     new InvocationHandler() {      // 执行代理对象方法时候触发
       @Override
       public Object invoke(Object proxy, Method method, Object[] args)
           throws Throwable {
         
         // 获取当前执行的方法的方法名
         String methodName = method.getName();
         // 方法返回值
         Object result = ;
         if ("find".equals(methodName)) {
           // 直接调用目标对象方法
           result = method.invoke(target, args);
         } else {
           System.out.println("开启事务...");
           // 执行目标对象方法
           result = method.invoke(target, args);
           System.out.println("提交事务...");
         }
         return result;
       }
     }
   );
   return proxy;
 }
}

现在,我们可以看看AOP的定义:面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时加入相关逻辑。

通过定义和前面代码我们可以发现3点:

1.AOP是基于动态代理模式。

2.AOP是方法级别的(要测试的方法不能为static修饰,因为接口中不能存在静态方法,编译就会报错)。

3.AOP可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。

使用jdk生成的动态代理的前提是目标类必须有实现的接口。但这里又引入一个问题,如果某个类没有实现接口,就不能使用JDK动态代理,所以Cglib代理就是解决这个问题的。

Cglib使用的前提是目标类不能为final修饰。因为final修饰的类不能被继承。另外它是通过继承自委托类来生成代理的,所以如果委托类是final的,就无法被代理了。

5、spring aop原理及实战

前文提到JDK代理和Cglib代理两种动态代理,优秀的Spring框架把两种方式在底层都集成了进去,我们无需担心自己去实现动态生成代理。那么,Spring是如何生成代理对象的?:

AOP 就是用的 CGLib 的形式来生成的,JDK 动态代理使用 Proxy 来创建代理类,增强逻辑写在 InvocationHandler.invoke() 里,CGlib 动态代理也提供了类似的 Enhance 类,增强逻辑写在 MethodInterceptor.intercept() 中,也就是说所有委托类的非 final 方法都会被方法拦截器拦截,在说它的原理之前首先来看看它怎么用的

1.创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。

2.如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用Cglib代理。然后从容器获取代理后的对象,在运行期植入"切面"类的方法。通过查看Spring源码,我们在DefaultAopProxyFactory类中,找到这样一段话。

简单的从字面意思看出,如果有接口,则使用Jdk代理,反之使用Cglib,这刚好印证了前文所阐述的内容。Spring AOP综合两种代理方式的使用前提有会如下结论:如果目标类没有实现接口,且classfinal修饰的,则不能进行Spring AOP编程!

知道了原理,现在我们将自己手动实现Spring的AOP:

package test.spring_aop_anno;

import org.aspectj.lang.ProceedingJoinPoint;

public interface IUserDao {
 void save();
}
//用于测试Cglib动态代理
class OrderDao {
 public void save() {
   //int i =1/0;用于测试异常通知
   System.out.println("保存订单...");
 }
}
//用于测试jdk动态代理
class UserDao implements IUserDao {
 public void save() {
   //int i =1/0;用于测试异常通知
   System.out.println("保存用户...");
 }
}
//切面类
class TransactionAop {
 public void beginTransaction() {
   System.out.println("[前置通知]  开启事务..");
 }
 public void commit() {
   System.out.println("[后置通知] 提交事务..");
 }
 public void afterReturing(){
   System.out.println("[返回后通知]");
 }
 public void afterThrowing(){
   System.out.println("[异常通知]");
 }
 public void arroud(ProceedingJoinPoint pjp) throws Throwable{
   System.out.println("[环绕前:]");
   pjp.proceed();             // 执行目标方法
   System.out.println("[环绕后:]");
 }
}

它是通过继承自委托类,重写委托类的非 final 方法(final 方法不能重载),并在方法里调用委托类的方法来实现代码增强的,它的实现大概是这样

可以看到它并不要求委托类实现任何接口,而且 CGLIB 是高效的代码生成包,底层依靠 ASM(开源的 java 字节码编辑类库)操作字节码实现的,性能比 JDK 强,所以 Spring AOP 最终使用了 CGlib 来生成动态代理

由于反射的效率比较低,所以 CGlib 采用了FastClass 的机制来实现对被拦截方法的调用。FastClass 机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法

6使用

1.Spring声明式事务管理配置。

2.Controller层的参数校验。

3.使用Spring AOP实现MySQL数据库读写分离案例分析

4.在执行方法前,判断是否具有权限。

5.对部分函数的调用进行日志记录。监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。

6.信息过滤,页面转发等等功能,博主一个人的力量有限,只能列举这么多,欢迎评论区对文章做补充。

Spring事务传播行为

Spring 在 TransactionDefinition 接口中规定了 7 种类型的事务传播行为。事务传播行为是 Spring 框架独有的事务增强特性,他不属于的事务实际提供方数据库行为。

事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

 public void methodA(){
    methodB();
    //doSomething
 }

 @Transaction(Propagation=XXX)
 public void methodB(){
    //doSomething
 }

图片

1Propagation.REQUIRED

在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

2Propagation.REQUIRES_NEW

在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

3Propagation.NESTED

在外围方法未开启事务的情况下Propagation.NESTEDPropagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。

在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务

1和3的区别

NESTED 和 REQUIRED 修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是 REQUIRED 是加入外围方法事务,所以和外围事务同属于一个事务,一旦 REQUIRED 事务抛出异常被回滚,外围方法事务也将被回滚。而 NESTED 是外围方法的子事务,有单独的保存点,所以 NESTED 方法抛出异常被回滚,不会影响到外围方法的事务。

2和3的区别

NESTED 和 REQUIRES_NEW 都可以做到内部方法事务回滚而不影响外围方法事务。但是因为 NESTED 是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而 REQUIRES_NEW 是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

Spring循环依赖

是什么

创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A…

还有一种特殊的循环依赖,自己依赖自己

抛出BeanCurrentlyInCreationException异常

什么样的循环依赖可以被处理
  1. 出现循环依赖的Bean必须要是单例
  2. 依赖注入的方式不能全是构造器注入的方式(很多博客上说,只能解决setter方法的循环依赖,这是错误的)
Spring如何解决循环依赖

首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。

翻阅Spring文档倒是没有找到三级缓存的概念,可能也是本土为了方便理解的词汇。

在Spring的DefaultSingletonBeanRegistry类中,你会赫然发现类上方挂着这三个Map:

  • singletonObjects 它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。
  • singletonFactories 映射创建Bean的原始工厂
  • earlySingletonObjects 映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance.

后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。

Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:

第一步,先获取到三级缓存中的工厂;

第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。

当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

二级缓存可以解决吗

如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

RPC

RPC,Remote Procedure Call 即远程过程调用,远程过程调用其实对标的是本地过程调用,本地过程调用你熟悉吧?

本机上内部的方法调用都可以称为本地过程调用,而远程过程调用实际上就指的是你本地调用了远程机子上的某个方法,这就是远程过程调用。

RPC 框架就是要实现像那小助手一样的东西,目的就是让我们使用远程调用像本地调用一样简单方便,并且解决一些远程调用会发生的一些问题

Dubbo

Dubbo 是阿里巴巴 2011年开源的一个基于 Java 的 RPC 框架,中间沉寂了一段时间,不过其他一些企业还在用 Dubbo 并自己做了扩展,比如当当网的 Dubbox,还有网易考拉的 Dubbok。

但是在 2017 年阿里巴巴又重启了对 Dubbo 维护。在 2017 年荣获了开源中国 2017 最受欢迎的中国开源软件 Top 3。

在 2018 年和 Dubbox 进行了合并,并且进入 Apache 孵化器,在 2019 年毕业正式成为 Apache 顶级项目。

目前 Dubbo 社区主力维护的是 2.6.x 和 2.7.x 两大版本,2.6.x 版本主要是 bug 修复和少量功能增强为准,是稳定版本。

而 2.7.x 是主要开发版本,更新和新增新的 feature 和优化,并且 2.7.5 版本的发布被 Dubbo 认为是里程碑式的版本发布,之后我们再做分析。

它实现了面向接口的代理 RPC 调用,并且可以配合 ZooKeeper 等组件实现服务注册和发现功能,并且拥有负载均衡、容错机制等。

大的三层分别为 Business(业务层)、RPC 层、Remoting,并且还分为 API 层和 SPI 层。

分为大三层其实就是和我们知道的网络分层一样的意思,只有层次分明,职责边界清晰才能更好的扩展

而分 API 层和 SPI 层这是 Dubbo 成功的一点,采用微内核设计+SPI扩展,使得有特殊需求的接入方可以自定义扩展,做定制的二次开发。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Mybatis SpringMVC 实战作业是一种结合了Spring框架、Mybatis框架以及SpringMVC框架的编程任务。在实战作业中,我们需要使用这三个框架实现一个完整的Web应用。 首先,我们需要使用Spring框架实现项目的基本配置和依赖注入功能。Spring框架可以帮助我们管理各个组件的生命周期和依赖关系,并提供了许多实用的特性,如AOP、事务管理等。 其次,我们需要使用Mybatis框架来简化数据库操作。Mybatis是一个持久层框架,可以通过配置文件或注解的方式,将Java对象映射到数据库中的表,并提供了丰富的查询语法和缓存机制。 最后,我们需要使用SpringMVC框架来处理Web请求和响应。SpringMVC是一个基于MVC设计模式的Web框架,可以帮助我们实现灵活的请求处理和页面跳转,还提供了对RESTful风格的支持。 在实战作业中,我们可以按照以下步骤进行操作: 1. 配置Spring框架,包括定义数据源、事务管理等。 2. 配置Mybatis框架,包括定义数据库连接信息、映射文件或注解等。 3. 创建数据库表和实体类,定义它们之间的映射关系。 4. 编写DAO接口和对应的Mapper文件或注解,实现数据库的增删改查操作。 5. 配置SpringMVC框架,包括定义Controller类、请求映射和视图解析器等。 6. 编写Controller类,处理具体的页面请求和业务逻辑。 7. 编写页面模板,实现页面的展示和用户交互效果。 通过完成该实战作业,我们可以深入理解Spring框架、Mybatis框架和SpringMVC框架的使用方法,并掌握它们在Web应用开发中的应用场景和优势。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值