Professional Documents
Culture Documents
必会设计模式
必会设计模式
1、六大原则
1. 【单一原则】(Single Responsibility Principle):一个类或者
一个方法只负责一项职责。
om
接口编程,(通过接口作为参数实现应用场景)。
(1)抽象就是接口或者抽象类,细节就是实现类;
c
(2)上层模块不应该依赖下层模块,两者应依赖其抽象;
s.
(3)抽象不应该依赖细节,细节应该依赖抽象。
s
la
注:通俗点就是说变量或者传参数,尽量使用抽象类,或者接
口。
lc
单一接口;(扩展为类也是一种接口,一切皆接口)
定义:a.客户端不应该依赖它不需要的接口;b.类之间依赖关系
应该建立在最小的接口上;
简单理解:复杂的接口,根据业务拆分成多个简单接口;(对
于有些业务的拆分多看看适配器的应用)
注:接口的设计粒度越小,系统越灵活,但是灵活的同时结构
复杂性提高,开发难度也会变大,维护性降低;。
2、设计模式的分类
c om
s.
s
1. 单例模式:某个类只能有一个实例,提供一个全局的访问点。
la
2. 简单工厂:一个工厂类根据传入的参量决定创建出那一种产品
lc
类的实例。
yd
3. 工厂方法:定义一个创建对象的接口,让子类决定实例化那个
类。
4. 抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体
类。
5. 建造者模式:封装一个复杂对象的构建过程,并可以按步骤构
造。
6. 原型模式:通过复制现有的实例来创建新的实例。
java.lang.Object#clone()
7. 适配器模式:将一个类的方法接口转换成客户希望的另外一个
接口。
8. 组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结
构。
9. 装饰模式:动态的给对象添加新的功能。
BufferedReader bufferedReader=new
om
BufferedReader(inputStreamReader);
10. 代理模式:为其他对象提供一个代理以便控制这个对象的访
c
问。
s.
11. 亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的
s
对象。
la
12. 外观模式:对外提供一个统一的方法,来访问子系统中的一群
lc
接口。
yd
13. 桥接模式:将抽象部分和它的实现部分分离,使它们都可以独
立的变化。将抽象与实现解耦。
jdbc中就是使用桥接模式的
14. 模板模式:定义一个算法结构,而将一些步骤延迟到子类实
现。
java.util.Collections#sort()
java.util.AbstractList#indexOf()
15. 解释器模式:给定一个语言,定义它的文法的一种表示,并定
义一个解释器。
java.util.Pattern,java.text.Format
16. 策略模式:定义一系列算法,把它们封装起来,并且使它们可
以相互替换。
java.util.Comparator
17. 状态模式:允许一个对象在其对象内部状态改变时改变它的行
为。
18. 观察者模式:对象间的一对多的依赖关系。
javax.servlet.http.HttpSessionAttributeListener
19. 备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
20. 中介者模式:用一个中介对象来封装一系列的对象交互。
om
21. 命令模式:将命令请求封装为一个对象,使得可以用不同的请
c
s.
求来进行参数化。
s
22. 访问者模式:在不改变数据结构的前提下,增加作用于一组对
la
象元素的新功能。
lc
23. 责任链模式:将请求的发送者和接收者解耦,使得多个对象都
有处理这个请求的机会。
yd
javax.servlet.Filter#doFilter()
24. 迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴
露该对象的内部结构。
java.util.Iterator
3、UML
UML(Unified Modeling Language)是一种统一建模语言,为面
向对象开发系统的产品进行说明、可视化、和编制文档的一种标准
语言。下面将对UML的九种图+包图的基本概念进行介绍以及各个
图的使用场景。
以往的开发中,特别迷信UML,当然开发过程中一定是设计先行,
但是现在的开发讲求灵活性,能用更简单的表示方法进行设计更
好,设计的目的是帮我我们更好的理解业务,更好的进行开发。
UML分静态图和动态图两种,其中常用的静态图5种,动态图4种。
常用的静态图:用例图、类图、包图、对象图、部署图
om
常用的动态图:顺序图,通信图(UML1.x 时称为协作图),状态
c
机图,活动图
s.
s
la
(1)类图:
lc
yd
类图(Class diagram)是显示了模型的静态结构,特别是模型中存在
的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时
性的信息。类图是面向对象建模的主要组成部分。它既用于应用程
序的系统分类的一般概念建模,也用于详细建模,将模型转换成编
程代码。类图也可用于数据建模。
c om
s.
s
在UML类图中,常见的有以下几种关系: 泛化(Generalization),
la
实现(Realization),关联(Association),聚合
lc
(Aggregation),组合(Composition),依赖(Dependency)
yd
1、泛化(Generalization)
【泛化关系】:是一种继承关系,表示一般与特殊的关系,它指定
了子类如何特化父类的所有特征和行为。例如:老虎是动物的一
种,即有老虎的特性也有动物的共性。
【箭头指向】:带三角箭头的实线,箭头指向父类
2、实现(Realization)
om
【实现关系】:是一种类与接口的关系,表示类是接口所有特征和
行为的实现
【箭头指向】:带三角箭头的虚线,箭头指向接口
c
s s.
la
lc
yd
3、关联(Association)
【关联关系】:是一种拥有的关系,它使一个类知道另一个类的属
性和方法;如:老师与学生,丈夫与妻子关联可以是双向的,也可
以是单向的。双向的关联可以有两个箭头或者没有箭头,单向的关
联有一个箭头。
【代码体现】:成员变量
【箭头及指向】:带普通箭头的实心线,指向被拥有者
上图中,老师与学生是双向关联,老师有多名学生,学生也可能有
多名老师。但学生与某课程间的关系为单向关联,一名学生可能要
上多门课程,课程是个抽象的东西它不拥有学生。
下图为自身关联:
omc
s s.
la
4、聚合(Aggregation)
lc
【聚合关系】:是整体与部分的关系,且部分可以离开整体而单独
yd
存在。如车和轮胎是整体和部分的关系,轮胎离开车仍然可以存
在。
聚合关系是关联关系的一种,是强的关联关系;关联和聚合在语法
上无法区分,必须考察具体的逻辑关系。
【代码体现】:成员变量
【箭头及指向】:带空心菱形的实心线,菱形指向整体
5、组合(Composition)
om
【组合关系】:是整体与部分的关系,但部分不能离开整体而单独
存在。如公司和部门是整体和部分的关系,没有公司就不存在部
门。
c
s.
组合关系是关联关系的一种,是比聚合关系还要强的关系,它要求
普通的聚合关系中代表整体的对象负责代表部分的对象的生命周
s
la
期。
lc
【代码体现】:成员变量
yd
【箭头及指向】:带实心菱形的实线,菱形指向整体
6. 依赖(Dependency)
【依赖关系】:是一种使用的关系,即一个类的实现需要另一个类
的协助,所以要尽量不使用双向的互相依赖.
【代码表现】:局部变量、方法的参数或者对静态方法的调用
【箭头及指向】:带箭头的虚线,指向被使用者
omc
s.
s
la
lc
(2)时序图:
yd
时序图(Sequence Diagram),又名序列图、循序图,是一种
UML交互图。它通过描述对象之间发送消息的时间顺序显示多个对
象之间的动态协作。它可以表示用例的行为顺序,当执行一个用例
行为时,其中的每条消息对应一个类操作或状态机中引起转换的触
发事件。
(3)用例图:
om
是用户与系统交互的最简表示形式,展现了用户和与他相关的用例
之间的关系。通过用例图,人们可以获知系统不同种类的用户和用
c
例。用例图也经常和其他图表配合使用。
ss.
la
lc
yd
二、工厂设计模式
工厂模式分为简单工厂模式,工厂方法模式和 抽象工厂模式 ,它们
都属于设计模式中的创建型模式。其主要功能都是帮助我们把对象
的实例化部分抽取了出来,目的是降低系统中代码耦合度,并且增
强了系统的扩展性。
还有一个很重要的原因,就是对象的创建过程比较复杂,使用工厂
模式进行封装,同时可以屏蔽掉对象的复杂的创建过程。
om
(1)简单工厂设计模式
c
s s.
简单工厂模式最大的优点在于实现对象的创建和对象的使用分离,
la
将对象的创建交给专门的工厂类负责,但是其最大的缺点在于工厂
lc
类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,
而且产品较多时,工厂方法代码将会非常复杂。
yd
简单工厂模式包含如下角色:
Factory:工厂角色
工厂角色负责实现创建所有实例的内部逻辑
Product:抽象产品角色
抽象产品角色是所创建的所有对象的父类,负责描述所有实例
所共有的公共接口
ConcreteProduct:具体产品角色
具体产品角色是创建目标,所有创建的对象都充当这个角色的
某个具体类的实例。
类图:
c om
s.
1、定义一个接口
s
/**
la
* @author itnanls(微信)
lc
* 我们的服务: 一路陪跑,顺利就业
*/
yd
创建一个奔驰类
/**
* @author itnanls(微信)
* 我们的服务: 一路陪跑,顺利就业
*/
public class Mate50 implements HuaweiPhone {
public void call() {
System.out.println("use a mate50 to make a
call.");
}
}
/**
om
* @author itnanls(微信)
* 我们的服务: 一路陪跑,顺利就业
*/
c
public class P50 implements HuaweiPhone {
s.
public void call() {
s
call.");
}
lc
}
yd
创建一个工厂
/**
* @author itnanls(微信)
* 我们的服务: 一路陪跑,顺利就业
*/
public class PhoneFactory {
public static HuaweiPhone getCar(String type){
if("p50".equalsIgnoreCase(type)){
//其中可能有很复杂的操作
return new P50();
}else if("mate50".equalsIgnoreCase(type)){
//其中可能有很复杂的操作
return new Mate50();
}else {
throw new RuntimeException("There are no
cell phones of this brand!");
}
}
}
测试
om
/**
* @author itnanls(微信)
* 我们的服务: 一路陪跑,顺利就业
c
*/
s.
public class Client {
public static void main(String[] args) {
s
HuaweiPhone p50 =
la
PhoneFactory.getCar("p50");
lc
p50.call();
HuaweiPhone mate50 =
yd
PhoneFactory.getCar("mate50");
mate50.call();
}
}
(2)工厂方法模式
我们说过java开发中要遵循开闭原则,如果将来有一天我想增加一
个新的车,那么必须修改CarFactory,就不太灵活。解决方案是使
用工厂方法模式。
工厂方法模式包含如下角色:
Product:抽象产品
ConcreteProduct:具体产品
Factory:抽象工厂
ConcreteFactory:具体工厂
c om
s s.
la
lc
yd
我们为每一个车都构建成一个工厂:
先抽象一个工厂接口
/**
* @author itnanls(微信)
* 我们的服务: 一路陪跑,顺利就业
*/
public interface Factory {
/**
* 统一的创建方法
* @return
*/
HuaweiPhone create();
}
om
然后针对每一个产品构建一个工厂方法
/**
c
* @author itnanls(微信)
s.
* 我们的服务: 一路陪跑,顺利就业
s
*/
la
//中间省略一万行代码
yd
/**
* @author itnanls(微信)
* 我们的服务: 一路陪跑,顺利就业
*/
public class P50Factory implements Factory {
public HuaweiPhone create() {
//中间省略一万行代码
return new P50();
}
}
应用场景
benz.run();
la
bmw.run();
yd
}
}
好处
此模式中,通过定义一个抽象的核心工厂类,并定义创建产品对象
的接口,创建具体产品实例的工作延迟到其工厂子类去完成。这样
做的好处是核心类只关注工厂类的接口定义,而具体的产品实例交
给具体的工厂子类去创建。当系统需要新增一个产品是,无需修改
现有系统代码,只需要添加一个具体产品类和其对应的工厂子类,
使系统的扩展性变得很好,符合面向对象编程的开闭原则。
缺点
工厂方法模式虽然扩展性好,但是增加了编码难度,大量增加了类
的数量,所以怎么选择还是看实际的需求。
(3)抽象工厂设计模式
c om
s.
抽象工厂模式包含如下角色:
s
AbstractFactory:抽象工厂
la
ConcreteFactory:具体工厂
lc
AbstractProduct:抽象产品
Product:具体产品
yd
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业
务、分类时,通过抽象工厂模式产生需要的对象是一种非常好的解
决方式。
类图:
1、定义产品抽象类
c om
s.
电脑产品线
s
la
/**
* @author itnanls(微信)
lc
* 我们的服务: 一路陪跑,顺利就业
yd
*/
public abstract class AbstractComputerProduct {
// 生产电脑产品的共享方法
public void sharedMethod(){
System.out.println("这是生产电脑的产品线。");
}
手机产品线
/**
* @author itnanls(微信)
* 我们的服务: 一路陪跑,顺利就业
*/
public abstract class AbstractPhoneProduct {
// 添加loge标签
public void labelling(){
System.out.println("这是小米的产品。");
}
om
2、定于具体产品
c
s.
我们定义两个华为的产品
s
AbstractComputerProduct {
lc
@Override
public void networking() {
yd
System.out.println("Use a mateBook16 to
connect to the Internet");
}
}
public class Mate60 extends AbstractPhoneProduct {
@Override
public void networking() {
System.out.println("Use a Mate60 to connect
to the Internet");
}
}
定义两个小米的产品
om
public void networking() {
System.out.println("Use a Mi12 to connect to
the Internet");
c
}
s.
}
s
la
lc
@Override
public void networking() {
System.out.println("Use a MiBookPro15 to
connect to the Internet");
}
}
3、定义抽象工厂
public abstract class AbstractFactory {
/**
* 生产手机的方法
* @return
*/
abstract AbstractPhoneProduct createPhone();
/**
* 生产电脑的方法
* @return
*/
abstract AbstractComputerProduct
createComputer();
}
c om
4、定义举起的产品工厂
s.
小米产品线的工厂
s
la
@Override
yd
AbstractPhoneProduct createPhone() {
return new Mi12();
}
@Override
AbstractComputerProduct createComputer() {
return new MiBookPro15();
}
}
华为产品线的工厂
public class HuaweiFactory extends AbstractFactory {
@Override
AbstractPhoneProduct createPhone() {
return new Mate60();
}
@Override
AbstractComputerProduct createComputer() {
return new MateBook16();
}
}
测试
factory.createPhone();
la
phone.networking();
}
lc
}
yd
这一次我们的设计相对来说比较复杂,增加了产品一族的概念。如
果我们想在某一个产品线下新增一个产品,和之前的工厂方法设计
模式一样,只需要增加相应的产品和工厂类即可,必会影响其他任
何产品。但是我们因为我们的设计会导致产品无法独立于产品族单
独存在。如果我们想增加一条的的产品线,比如联想,也不需要对
其他代码进行修改。
三、创建者设计模式
分离对象子组件的单独构造(由Buider来负责)和装配(由
Director负责)。从而可以构造出复杂的对象。这个模式适用于:
某个对象的构建过程复杂的情况下使用。
由于实现构造和装配的解耦。不同的构造器,相同的装配,也可以
做出不同的对象;相同的构造器,不同的装配顺序也可以做出不同
的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的
复用。
最典型的就是我们的StringBuilder和StringBuffer。
om
建造者模式包含如下角色:
c
s.
Builder:抽象建造者
ConcreteBuilder:具体建造者
s
Director:指挥者
la
Product:产品角色
lc
yd
1、模拟一个复杂对象
public class Computer {
// 鼠标
public String mouse;
// 键盘
public String keyboard;
// cpu
public String cpu;
// 内存
public String memoryBank;
// 主板
public String mainBoard;
...省略getter和setter
}
c
2、我们可以在Computer类中添加一个静态内部类,专门负责构建
om
s.
工作
s
la
om
return this;
} c
public Computer build(){
s.
return computer;
s
}
la
}
lc
yd
我们在构造电脑的时候,就可以使用这种方案了。
思考这种构造方法和普通的方法有什么区别呢?
1、当我们要构造的对象相对来说比较复杂,成员变量比较多,使
用构造器不容易进行灵活配置。
2、构造的过程可以进行灵活的编排,可以随时舍掉或新增过程,
有些时候可能出现对顺序性的要求,构建的顺序不同,结果不同。
3、单个的构建过程其实也比较复杂,比如,主板的构建,本身也
是一个复杂过程,cpu的选型也是复杂过程,使用setter方法不容易
表达复杂性。
创建者模式是一个对对象的构建过程“精细化”的构建过程,每个部
om
分的构建可能是变化的,但是对象的组织过程是固定的,通过这种
统一的创建方式,无疑增加了我们设计上的灵活性,当我们在构建
c
复杂对象的时候,我们如果发现每个部分可能都是变化的,并且是
s.
多个不同的构建步骤的时候,我们可以考虑使用创建者模式。
s
相比工厂和抽象工厂模式区别还是很大的,创建者适合这类复杂对
la
象的创建,对于抽象工厂可能就无法完成这样的组装工作,而且创
lc
建者模式是把复杂对象的内部创建方法进行调用,组织协调了对象
的各个部分前后顺序的控制。简单的描述创建者就是这样的情况。
yd
四、代理设计模式
代理模式分为静态代理和动态代理。代理的核心功能是方法增
强。
1、静态代理
静态代理角色分析
抽象角色 : 一般使用接口或者抽象类来实现
真实角色 : 被代理的角色
代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属
的操作
客户 : 使用代理角色来进行一些操作 .
代码实现
写一个接口
}
la
定义男歌手
lc
yd
public class MaleSinger implements Singer{
@Override
public void sing() {
System.out.println(this.name + "开始唱歌
了!");
}
}
定义经纪人
c om
/**
s.
* @author IT楠老师
s
* @date 2020/5/28
la
*/
public class Agent implements Singer {
lc
yd
@Override
public void sing() {
System.out.println("节目组找过来!需要演出,谈好
演出费用。。。。。");
singer.sing();
System.out.println("结算费用,下一次合作预
约。。。。。。");
}
}
/**
* @author IT楠老师
* @date 2020/5/28
*/
public class Client {
om
}
} c
分析:在这个过程中,你直接接触的就是鹿晗的经济人,经纪人在
s.
鹿晗演出的前后跑前跑后发挥了巨大的作用。
s
la
除了实现共同的接口,我们还能使用继承类的方式
lc
yd
@Override
public void sing() {
System.out.println("开始唱歌了-----------");
maleSinger.sing();
System.out.println("结束唱歌了-----------");
}
}
优点
鹿晗还是鹿晗,没有必要为了一下前置后置工作改变鹿晗这个
类
公共的统一问题交给代理处理
公共业务进行扩展或变更时,可以更加方便
c om
s.
这不就是更加符合开闭原则,单一原则吗?
s
缺点 :
la
lc
每个类都写个代理,麻烦死了。
yd
2、动态代理
动态代理的角色和静态代理的一样
动态代理的代理类是动态生成的 ,静态代理的代理类是我们写
的
基于接口的动态代理----JDK动态代理
基于类的动态代理--cglib
动态代理就是当有大量的类需要执行一些共同代码时,我们自己写
太麻烦,那能不能直接使用java代码,自动生成一个类帮助我们批
量的增强某些方法。
(1)JDK原生的动态代理
JDK的动态代理需要了解两个类
【InvocationHandler:调用处理程序】 c om
Object invoke(Object proxy, 方法 method, Object[]
s.
args);
s
//参数
la
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法
lc
对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代
yd
理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或
null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器
类的实例中,例如java.lang.Integer或java.lang.Boolean 。
【Proxy : 代理】
Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
代码实现
抽象角色和真实角色和之前的一样!
还是歌星和男歌星
/**
* @author IT楠老师
* @date 2020/5/21
* 经纪人
*/
public class Agent implements InvocationHandler {
/**
* 设置代理的经济人
* @param singer
c om
s.
*/
public void setSinger(Singer singer) {
s
this.singer = singer;
la
}
lc
@Override
yd
/**
* 获取一个代理对象
* @return
*/
public Object getProxy(){
return
Proxy.newProxyInstance(this.getClass().getClassLoade
r(),
new Class[]{Singer.class},this);
}
}
Client . java
/**
om
* @author IT楠老师
* @date 2020/5/21
*/
c
public class Client {
s.
public static void main(String[] args) {
s
la
agent.setSinger(luhan);
Singer singer = (Singer)agent.getProxy();
singer.sing();
}
}
//该设置用于输出jdk动态代理产生的类
System.getProperties().put("sun.misc.ProxyGenerator.
saveGeneratedFiles", "true");
(2)基于cglib
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
<build>
om
<plugins>
<plugin>
c
<groupId>org.apache.maven.plugins</groupId>
s.
<artifactId>maven-compiler-
plugin</artifactId>
s
<version>3.8.1</version>
la
<configuration>
lc
<source>1.8</source>
<target>1.8</target>
yd
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
om
public static void main(String[] args) {
Enhancer enhancer=new Enhancer();
c
enhancer.setSuperclass(MaleSinger.class);
s.
enhancer.setCallback(new MethodInterceptor() {
s
System.out.println("---------");
yd
Object invoke =
methodProxy.invokeSuper(o,objects);
System.out.println("++++++++++");
return invoke;
}
});
MaleSinger maleSinger =
(MaleSinger)enhancer.create(new Class[]
{String.class},new Object[]{"小李"});
maleSinger.sing();
}
/**
* All generated proxied methods call this
method instead of the original method.
* The original method may either be invoked by
normal reflection using the Method object,
* or by using the MethodProxy (faster).
* @param obj "this", the enhanced object
* @param method intercepted Method
* @param args argument array; primitive types
are wrapped
* @param proxy used to invoke super (non-
intercepted method); may be called
* as many times as needed
* @throws Throwable any exception may be
om
thrown; if so, super method will not be invoked
* @return any value compatible with the
signature of the proxied method. Method returning
c
void will ignore this value.
s.
* @see MethodProxy
s
*/
la
MethodProxy proxy)
yd
throws Throwable;
//该设置用于输出cglib动态代理产生的类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATI
ON_PROPERTY, "D:\\class");
1. Java动态代理只能够对接口进行代理,不能对普通的类进行代理
(因为所有生成的代理类的父类为Proxy,Java类继承机制不允
许多重继承);
2. CGLIB能够代理普通类;
3. Java动态代理使用Java原生的反射API进行操作,在生成类上比
较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执
行过程中比较高效。
3、ASM畅聊动态代理的本质
1、介绍
ASM是一个java字节码操纵和分析框架,它可以直接以二进制的形
式修改class或动态生成class。官网
2、使用 c
接下来我们使用asm框架实现一个和JDK动态代理同样的功能。
om
s.
3、引入maven依赖
s
la
<dependency>
<groupId>org.ow2.asm</groupId>
lc
<artifactId>asm-util</artifactId>
yd
<version>8.0.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>8.0.1</version>
</dependency>
4、定义目标对象接口
/**
* 可以唱歌的
*/
public interface Singable {
/**
* 唱歌
*/
void sing();
}
5、定义目标对象
/**
* 歌手
om
*/
public class Singer implements Singable {
@Override
c
public void sing() {
s.
System.out.println("I am singing...");
s
}
la
}
lc
6、定义自己的类加载器
yd
/**
* 自定义类加载器
*/
public class MyClassLoader extends ClassLoader {
public MyClassLoader() {
super(Thread.currentThread().getContextClassLoader(
));
}
/**
* 将字节数组转化为Class对象
*
* @param name 类全名
* @param data class数组
* @return
*/
public Class<?> defineClassForName(String name,
byte[] data) {
return this.defineClass(name, data, 0,
data.length);
}
7、创建代理
om
/**
* @author itnanls(微信)
* 我们的服务: 一路陪跑,顺利就业
c
*/
s.
public class SingerAgentDump implements Opcodes {
s
la
// 生成一个class的字节数组
public static byte[] dump() throws Exception {
lc
yd
// 定义class版本1.8,访问权限,类名,继承类,实现接
口等信息
classWriter.visit(V1_8, ACC_PUBLIC |
ACC_SUPER, "com/ydlclass/SingerAgent", null,
"java/lang/Object", new String[]
{"com/ydlclass/Singable"});
classWriter.visitSource("SingerAgent.java",
null);
{
// 定义私有属性
fieldVisitor =
classWriter.visitField(ACC_PRIVATE, "delegate",
"Lcom/ydlclass/Singable;", null, null);
fieldVisitor.visitEnd();
}
{
// 定义构造器
methodVisitor =
om
classWriter.visitMethod(ACC_PUBLIC, "<init>", "
(Lcom/ydlclass/Singable;)V", null, null);
methodVisitor.visitParameter("delegate",
c
0);
s.
methodVisitor.visitCode();
s
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(10,
lc
label0);
yd
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL,
"java/lang/Object", "<init>", "()V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(11,
label1);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitFieldInsn(PUTFIELD,
"com/ydlclass/SingerAgent", "delegate",
"Lcom/ydlclass/Singable;");
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(12,
label2);
methodVisitor.visitInsn(RETURN);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLocalVariable("this",
"Lcom/ydlclass/SingerAgent;", null, label0, label3,
0);
methodVisitor.visitLocalVariable("delegate",
"Lcom/ydlclass/Singable;", null, label0, label3, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
om
}
{
// 定义方法sing
c
methodVisitor =
s.
classWriter.visitMethod(ACC_PUBLIC, "sing", "()V",
s
null, null);
la
methodVisitor.visitCode();
Label label0 = new Label();
lc
methodVisitor.visitLabel(label0);
yd
methodVisitor.visitLineNumber(16,
label0);
methodVisitor.visitFieldInsn(GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("before
sing...");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
"java/io/PrintStream", "println", "
(Ljava/lang/String;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(17,
label1);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD,
"com/ydlclass/SingerAgent", "delegate",
"Lcom/ydlclass/Singable;");
methodVisitor.visitMethodInsn(INVOKEINTERFACE,
"com/ydlclass/Singable", "sing", "()V", true);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(18,
label2);
methodVisitor.visitFieldInsn(GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("after
om
sing...");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
c
"java/io/PrintStream", "println", "
s.
(Ljava/lang/String;)V", false);
s
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(19,
lc
label3);
yd
methodVisitor.visitInsn(RETURN);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLocalVariable("this",
"Lcom/ydlclass/SingerAgent;", null, label0, label4,
0);
methodVisitor.visitMaxs(2, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
// 将字节数组加载为class
public static Singable newProxyInstance(Singable
delegate) throws Exception {
String className =
"com.ydlclass.SingerAgent";
byte[] classData = dump();
Class<?> aClass = new
MyClassLoader().defineClassForName(className,
classData);
return (Singable)
aClass.getDeclaredConstructor(Singable.class).newIns
tance(delegate);
}
}
面的代码就是通过转换得到的。
c om
ASM提供了一个功能,可以将普通的java代码转换成ASM代码,上
s.
8、定义要转换的java代码
s
la
/**
* 歌手经纪人
lc
*/
public class SingerAgent implements Singable {
yd
@Override
public void sing() {
System.out.println("before sing...");
delegate.sing();
System.out.println("after sing...");
}
}
9、转换
10、客户端调用
om
public class Client {
public static void main(String[] args) throws
Exception {
c
Singable singerAgent =
s.
SingerAgentDump.newProxyInstance(new Singer());
singerAgent.sing();
s
la
}
lc
}
yd
输出结果为
before sing...
I am singing...
after sing...
符合预期,说明我们通过ASM创建的class是可以正常工作的,但对
比JDK创建动态代理,ASM涉及到大量对java底层字节码的操作,
我们对字节码越熟悉,使用ASM就会越容易。
11、遇到的问题
// 将字节数组加载为class
public static SingerAgent newProxyInstance(Singable
delegate) throws Exception {
String className =
"com.imooc.sourcecode.java.dynamicproxy.asm.test3.Si
ngerAgent";
byte[] classData = dump();
Class<?> aClass = new
MyClassLoader().defineClassForName(className,
classData);
return (SingerAgent)
aClass.getDeclaredConstructor(Singable.class).newIns
tance(delegate);
}
om
刚开始我将加载后的class强转为了SingerAgent 类,但报异常
c
Exception in thread "main"
s.
java.lang.ClassCastException: class
s
com.imooc.sourcecode.java.dynamicproxy.asm.test3.Sin
la
gerAgent
yd
(com.imooc.sourcecode.java.dynamicproxy.asm.test3.Si
ngerAgent is in unnamed module of loader
com.imooc.sourcecode.java.dynamicproxy.asm.test3.MyC
lassLoader @4590c9c3;
com.imooc.sourcecode.java.dynamicproxy.asm.test3.Sin
gerAgent is in unnamed module of loader 'app')
at
com.imooc.sourcecode.java.dynamicproxy.asm.test3.Sin
gerAgentDump.newProxyInstance(SingerAgentDump.java:9
8)
at
com.imooc.sourcecode.java.dynamicproxy.asm.test3.Cli
ent.main(Client.java:5)
原因是因为生成的SingerAgent 类和原有的SingerAgent 类不是同
一个类加载器加载的,所以不能转换。
12、ASM使用场景
ASM是一个非常强大的字节码工具,以下都使用到了ASM
s
la
lc
五、实战
yd
1、hikari.properties
username=root
password=root
jdbcUrl=jdbc:mysql://127.0.0.1:3306/ssm?
useUnicode=true&characterEncoding=utf8&serverTimezon
e=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=
false
driverClassName=com.mysql.cj.jdbc.Driver
druid.properties
url=jdbc:mysql://127.0.0.1:3306/ssm?
useUnicode=true&characterEncoding=utf8&serverTimezon
e=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=
false
username=root
password=root
driverClassName=com.mysql.cj.jdbc.Driver
2、mybatis-config.properties
c om
s.
<mybatis>
s
<dataSource>hikari</dataSource>
la
<mapper>mapper/userMapper.xml</mapper>
<mapper>mapper/studentMapper.xml</mapper>
lc
</mybatis>
yd
3、UserMapper.xml
<mapper namespace="cn.itnanls.UserDao">
<insert id="saveUser"
resultType="cn.itnanls.User"
paramType="cn.itnanls.User">
insert into user values(?,?,?)
</insert>
<select id="findUser"
resultType="cn.itnanls.User"
paramType="cn.itnanls.User">
select * from user where id = ?
</select>
</mapper>
4、User
c om
s.
/**
s
* @author zn
la
* @date 2021/1/25
*/
lc
yd
public User() {
}
om
public void setUsername(String username) {
this.username = username;
}
c
s.
public String getPassword() {
s
return password;
la
}
lc
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
5、UserDao
/**
* @author zn
* @date 2021/1/25
*/
public interface UserDao {
om
6、DaoWrapper
c
s.
/**
s
* 用于描述一个Dao的方法的必要条件
la
* @author zn
lc
* @date 2021/1/25
*/
yd
/**
* 类型,insert|update|delete
*/
private String type;
/**
* 返回值的类型
*/
private String resultType;
/**
* 参数的类型
*/
private String paramType;
/**
* sql语句
*/
private String sql;
om
public String getType() {
return type;
}
c
s.
public void setType(String type) {
s
this.type = type;
la
}
lc
return resultType;
}
@Override
public String toString() {
return "DaoWrapper{" +
"type='" + type + '\'' +
", resultType='" + resultType + '\''
om
+
", paramType='" + paramType + '\'' +
", sql='" + sql + '\'' +
c
'}';
s.
}
s
}
la
lc
7、DataSourceFactory
yd
/**
* 数据源工厂
* 简单工厂的应用
* @author zn
* @date 2021/1/25
*/
public class DataSourceFactory {
om
} else if ("druid".equals(type)){
try {
c
s.
properties.load(DataSourceFactory.class.getClassLoa
s
der().getResourceAsStream("druid.properties"));
la
} catch (IOException e) {
e.printStackTrace();
lc
}
yd
return dataSource;
}
8、Session
/**
* 会话对象
* @author zn
* @date 2021/1/25
*/
public class Session {
/**
* 每个会话持有一个链接
om
*/
private Connection connection;
/**
c
* 当前会话的上下文
s.
*/
s
= new HashMap<>(8);
lc
/**
* 拿到一个包装类
* @param clazz
* @param <T>
* @return
*/
public <T> T getMapper(Class<T> clazz) {
T t = (T)
Proxy.newProxyInstance(this.getClass().getClassLoade
r(),
new Class[]{clazz},
new SQLHandler(connection,
clazz,env.get(clazz.getName())));
return t;
}
// 开始会话
public void begin() {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
om
e.printStackTrace();
}
}
c
s.
// 提交
s
try {
connection.commit();
lc
} catch (SQLException e) {
yd
e.printStackTrace();
}
}
// 回滚
public void rollback() {
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
9、SessionFactory
/**
* @author zn
* @date 2021/1/25
*/
public class SessionFactory {
= new HashMap<>(8);
c om
private Map<String, Map<String, DaoWrapper>> env
s.
public SessionFactory(String config) {
loadXml(config);
s
}
la
lc
// 打开一个会话
public Session openSession() {
yd
// 加载资源环境
public void loadXml(String config) {
try {
SAXReader reader = new SAXReader();
Document configDom =
reader.read(Session.class.getClassLoader().getResour
ceAsStream(config));
Element configRoot =
configDom.getRootElement();
String dataSourceType =
configRoot.element("dataSource").getTextTrim();
dataSource =
DataSourceFactory.createDataSource(dataSourceType);
List mapperElements =
configRoot.elements("mapper");
List<String> mapperPaths = new
ArrayList<>();
om
for (Object element : mapperElements) {
Element mapper = (Element) element;
c
mapperPaths.add(mapper.getTextTrim());
s.
}
s
la
new HashMap<>(2);
yd
Document document =
reader.read(Session.class.getClassLoader().getResour
ceAsStream(mapperPath));
Element root =
document.getRootElement();
String namespace =
root.attribute("namespace").getValue();
Iterator iterator =
root.elementIterator();
while (iterator.hasNext()) {
Element el = (Element)
iterator.next();
String type = el.getName();
String id =
el.attribute("id").getValue();
String resultType =
el.attribute("resultType").getValue();
String paramType =
el.attribute("paramType").getValue();
String sqlStr =
el.getTextTrim();
wrapper.put(id, new
DaoWrapper(type, resultType, paramType, sqlStr));
}
env.put(namespace, wrapper);
}
om
} catch (DocumentException e) {
e.printStackTrace();
}
c
s.
}
s
}
la
lc
yd
10、SQLHandler
/**
* @author zn
* @date 2021/1/25
*/
public class SQLHandler implements InvocationHandler
{
/**
* 需传入一个链接
*/
private Connection connection;
/**
* 需传入一个dao的类型
*/
private Class clazz;
/**
* 需传入一个独立的环境
*/
private Map<String, DaoWrapper> env;
om
this.clazz = clazz;
this.env = env;
}
c
s.
/**
s
* 生成代理对象
la
* @param proxy
* @param method
lc
* @param args
yd
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method
method, Object[] args) throws Throwable {
// 拿到包装
DaoWrapper wrapper =
env.get(method.getName());
PreparedStatement statement =
connection.prepareStatement(wrapper.getSql());
// 对每一种sql语句进行独立的操作
if ("insert".equals(wrapper.getType())) {
String paramType =
wrapper.getParamType();
// 暂定传入一个对象
Class<?> clazz = args[0].getClass();
Field[] fields =
clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
fields[i].setAccessible(true);
statement.setObject(i + 1,
fields[i].get(args[0]));
}
om
return statement.executeUpdate();
} else if
c
("delete".equals(wrapper.getType())) {
s.
for (int i = 0; i < args.length; i++) {
s
statement.setObject(i + 1, args[i]);
la
}
return statement.executeUpdate();
lc
yd
} else if
("select".equals(wrapper.getType())) {
for (int i = 0; i < args.length; i++) {
statement.setObject(i + 1, args[i]);
}
ResultSet result =
statement.executeQuery();
List list = new ArrayList();
while (result.next()) {
Class<?> clazz =
Class.forName(wrapper.getResultType());
Object object = clazz.newInstance();
Field[] fields =
clazz.getDeclaredFields();
for (int i = 0; i < fields.length;
i++) {
fields[i].setAccessible(true);
fields[i].set(object,
result.getObject(fields[i].getName()));
}
list.add(object);
}
return list;
}
return null;
}
}
com
s s.
la
lc
yd