实训Day04

今日知识

  • 代理
  • 动态SQL

代理

什么是代理

代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务

作用

  • 增强
  • 保护

静态代理

在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了

案例

目标对象所需实现的接口

1
2
3
4
public interface UserService {
public void select();
public void update();
}

目标对象

1
2
3
4
5
6
7
8
public class UserServiceImpl implements UserService {  
public void select() {
System.out.println("查询 selectById");
}
public void update() {
System.out.println("更新 update");
}
}

代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserServiceProxy implements UserService {
private UserService target; // 被代理的对象

public UserServiceProxy(UserService target) {
this.target = target;
}
public void select() {
before();
target.select(); // 这里才实际调用真实主体角色的方法
after();
}
public void update() {
before();
target.update(); // 这里才实际调用真实主体角色的方法
after();
}

private void before() { // 在执行方法之前执行
System.out.println(String.format("log start time [%s] ", new Date()));
}
private void after() { // 在执行方法之后执行
System.out.println(String.format("log end time [%s] ", new Date()));
}
}

缺点

  • 代理多个类时,代理对象需要实现与目标对象一致的接口
    • 只维护一个代理类,但是代理类接口过多会过于庞大
    • 维护多个代理类,会产生过多的代理类
  • 接口需要增加、删除、修改时,目标对象与代理类都需要同时修改,不易维护

动态代理

在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件

原理

JVM类加载过程主要分为五个阶段:

  1. 加载
  2. 验证
  3. 准备
  4. 解析
  5. 初始化。

完成以下事情:

  1. 通过一个类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

关于第一点,可以通过运行时计算生成类的二进制字节流,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流

字节码操作类库

  • Apache BCEL
  • ObjectWeb ASM
  • CGLib
  • Javassist

JDK动态代理

通过实现接口的方式

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 新建一个代理实例
public static Object newProxyInstance(
ClassLoader loader, // 类加载器
Class<?>[] interfaces, // 要实现代理的接口
InvocationHandler h // 会调用 h 里面的 invoke 方法执行
) throws IllegalArgumentException


// 实现动态代理的接口
public class h implements InvocationHandler {
private final IVehical vehical; // 被代理的对象
public h (IVehical vehical){
this.vehical = vehical;
}

public Object invoke(
Object proxy, // 代理对象,而不是真实对象!
Method method, // 所调用的方法类
Object[] args // 所调用方法的参数列表
) throws Throwable {
System.out.println("---------before-------");
Object invoke = method.invoke(vehical, args);
// 以被代理对象为主体执行对应的方法并获得结果
System.out.println("---------after-------");

return invoke;
}
}

CGLib动态代理

通过继承类的方式

CGLIB(Code Generation Library) 是一个开源、高性能、高质量的 Code 生成类库(代码生成包)

它可以在运行期扩展 Java 类与实现 Java 接口。 Hibernate 用它实现 PO(Persistent Object 持久化对象)字节码的动态生成,Spring AOP用它提供方法的interception(拦截)

案例
导入 CGLib 依赖

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>

实现 MethodInterceptor 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyInterceptor implements MethodInterceptor {
@Override
public Object intercept(
Object obj, // 真实对象
Method method, // 被代理的方法
Object[] objects, // 被代理方法的参数列表
MethodProxy methodProxy // 方法代理对象
) throws Throwable {
Object result = methodProxy.invokeSuper(obj, objects);
// 注意这里是调用invokeSuper而不是invoke,否则死循环;
// methodProxy.invokeSuper执行的是真实对象的方法
// method.invoke执行的是代理对象的方法;
return result;
}
}

JDK和CGLib

  1. JDK 被代理和代理类必须实现接口(同级),CGLib 代理类继承被代理类的接口(上下级)
  2. JDK 原生支持,CGLib 需要引入第三方库
  3. CGLib 采用方法拦截的计数,拦截父类所有方法的调用

Mybatis代理

通过 JDK 动态代理的方式实现

根据 .xml 文件设计好对应 mapper 接口
再通过 SqlSession 对象的 getMapper 方法获取代理类
即可调用代理类的方法(.xml 文件中的 id 和 mapper 接口的方法名对应)实现对数据库的操作

案例

1
2
3
4
5
6
7
8
9
10
11
public List<User> findAll() {
SqlSession session = SessionFactory.openSession();
// 获得 session 对象
UserMapper userMapper = session.getMapper(UserMapper.class);
// 通过 session 的 getMapper 方法获取 UserMapper 的代理对象
List<User> users = userMapper.findAll();
// 调用代理的方法
SessionFactory.Close(session);
// 关闭连接
return users;
}

动态SQL

if标签

如果 if 标签内的 test 成立,则将 if 块内的语句填入 SQL 语句

1
2
3
4
<if test="o_key != null and o_key != ''">
<!-- 判断传入参数的属性值是否符合条件 -->
and/or t_key like/= #{o_key}
</if>

where标签

where 标签只会在至少有一个子元素返回了SQL语句时, 才会向 SQL 语句中添加 where,并且如果 where之后是以 and 或 or 开头,会自动将其删掉。

1
2
3
4
5
6
7
8
<where>
<if test="o_key1 != null and o_key1 != 0">
AND t_key1 = #{o_key1}
</if>
<if test="o_key2 != null and o_key2 != ''">
AND t_key2 = #{o_key2}
</if>
</where>

chose标签

choose 标签是按顺序判断其内部 when 标签中的 test 条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。类似于 Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default

1
2
3
4
5
6
7
8
9
10
<choose>  
<when test="">
</when >
<when test="">
</when >
<when test="">
</when >
<otherwise>
</otherwise>
</choose>

set标签

当 update 语句中没有使用 if 标签时,如果有一个参数为 null,都会导致错误。
当在 update 语句中使用if标签时,如果前面的if没有执行,则或导致逗号多余错误。使用 set 标签可以将动态的配置 SET 关键字,并剔除追加到条件末尾的任何不相关的逗号。使用 if+set 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值

1
2
3
4
5
6
7
<set>  
<if test="o_key1 != null and o_key1 != 0">
t_key1 = #{o_key1} ,
</if>
<if test="">
</if>
</set>

trim标签

一般用于去除 SQL 语句中多余的 AND | OR关键字,逗号,或者给 SQL 语句前拼接 “Where“、“Set“以及“Values(“ 等前缀,或者添加“)“等后缀,可用于选择性插入、更新、删除或者条件查询等操作

1
2
<trim prefix="拼接前缀" suffix="拼接后缀" suffixOverrides="去除前缀" prefixOverride="去除后缀">
</trim>

实训Day04
http://yjh-2860674406.github.io/2023/07/08/编程/实训/Day04/
Author
Ye JinHua
Posted on
July 8, 2023
Licensed under