结构性模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
适配器模式
适配器模式(Adapter Pattern)
- 充当两个不兼容接口之间的桥梁,属于结构型设计模式。
- 目的是将一个类的接口转换为另一个接口,使得原本不兼容的类可以协同工作
- 用户调用适配器转化出来的目标方法,适配器再调用被适配的方法,这样用户就看不到被配置者,感觉只是和目标方法交互,达到了解耦
- 适配器模式主要分为三类,是根据被适配者的类是怎么给到适配器来命名的:
- 类适配器模式,适配器继承被适配的类
- 对象适配器模式,适配器持有一个被适配者的对象
- 接口适配器模式,适配器将被适配者作为接口实现
- 使用场景
- 需要使用现有类,但其接口不符合系统需求。
- 希望创建一个可复用的类,与多个不相关的类(包括未来可能引入的类)一起工作,这些类可能没有统一的接口。
- 通过接口转换,将一个类集成到另一个类系中。
- 实现方式
- 继承或依赖:推荐使用依赖关系,而不是继承,以保持灵活性。
适配器模式包含以下几个主要角色:
- 目标接口(Target):定义客户需要的接口。
- 适配者类(Adaptee):定义一个已经存在的接口,这个接口需要适配。
- 适配器类(Adapter):实现目标接口,并通过组合或继承的方式调用适配者类中的方法,从而实现目标接口。
类适配器模式
优点
- 促进了类之间的协同工作,即使它们没有直接的关联。
- 提高了类的复用性。
- 增加了类的透明度。
- 提供了良好的灵活性,可以重写被适配的类中的方法。
缺点
- 过度使用适配器可能导致系统结构混乱,难以理解和维护。
- 在Java中,由于只能继承一个类,因此只能适配一个类,且目标类必须是抽象的。
- 而且被适配的类的所有方法都会暴露出来,增加了使用成本
java
/**
* 被适配的类
*/
public class AdaptationTest {
public Integer test(int a, int b) {
return a + b;
}
}
/**
* 目标接口
*/
public interface ITargetInterTest {
// 客户需要的接口
Double work(int a, int b);
}
/**
* 适配器
*/
public class Adaptation extends AdaptationTest implements ITargetInterTest {
@Override
public Double work(int a, int b) {
// 对被适配者进行处理,达到用户的需求
return (test(a, b) * 1.2) + 100;
}
}
/**
* 用户
*/
public class UseTest {
public static void main(String[] args) {
Adaptation adaptation =new Adaptation();
Double work = adaptation.work(10, 20);
System.out.println(work);
}
}
对象适配器模式
对象适配器
- 对象适配器思路和类适配器模式相同,只不过是不在继承被适配的类,而是持有这个类的实例,来解决兼容性问题
- 根据"合成复用原则",再系统中尽量使用关联关系来替代继承关系
java
/**
* 被适配的类
*/
public class AdaptationTest {
public Integer test(int a, int b) {
return a + b;
}
}
/**
* 目标接口
*/
public interface ITargetInterTest {
// 客户需要的接口
Double work(int a, int b);
}
/**
* 对象适配器,持有被适配的对象
*/
public class AdaptationPloy implements ITargetInterTest {
private AdaptationTest adaptationTest;
public AdaptationPloy(AdaptationTest adaptationTest) {
this.adaptationTest = adaptationTest;
}
@Override
public Double work(int a, int b) {
// 对被适配者进行处理,达到用户的需求
return (adaptationTest.test(a, b) * 1.2) + 100;
}
}
/**
* 用户
*/
public class UseTest {
public static void main(String[] args) {
ITargetInterTest a1 =new AdaptationPloy(new AdaptationTest());
System.out.println(a1.work(30,10));
}
}
接口适配器模式
接口适配器模式
- 当不需要全部实现接口提供的方法时,可以先设计一个抽象类实现接口,并为接口中的方法提供默认实现(空方法),那么该抽象类的子类就可以有选择的覆盖父类的某些方法来实现需求
java
/**
* 有很多方法的接口
*/
public interface Inter1 {
void a();
void b();
void c();
}
/**
* 默认实现
*/
public abstract class AbsClass implements Inter1{
@Override
public void a() {
System.out.println("默认的实现a");
}
@Override
public void b() {
System.out.println("默认的实现b");
}
@Override
public void c() {
System.out.println("默认的实现c");
}
}
/**
* 因为抽象类已经默认实现了接口方法,所以只实现需要使用的方法即可
*/
public class Test {
public static void main(String[] args) {
Inter1 inter = new AbsClass() {
public void a() {
System.out.println("子类自行实现想要用到的方法");
}
};
inter.a();
inter.b();
inter.c();
}
}
桥接模式
桥接模式
- 桥接(Bridge)模式,属于结构型设计模式
- 基于类的最小设计原则,将抽象和实现放在不同的类层次中,通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
- 有一个桥接的接口,使得实体类的功能独立于接口实现类,这两种类型的类可被结构化改变而互不影响。
- 桥接模式的目的是将抽象与实现分离,使它们可以独立地变化,该模式通过将一个对象的抽象部分与它的实现部分分离,使它们可以独立地改变。
- 通过组合,而不是继承的方式,将抽象和实现的部分连接起来。
使用场景
- 当系统可能从多个角度进行分类,且每个角度都可能独立变化时,桥接模式是合适的。
以下是桥接模式的几个关键角色:
- 抽象类(Abstraction):定义抽象接口,通常包含对实现接口的引用(聚合关系)
- 扩展抽象(Refined Abstraction):对抽象的扩展,可以是抽象类的子类或具体实现类。
- 行为实现类(Implementor):定义实现接口,提供基本操作的接口。
- 行为实现的具体实现类(Concrete Implementor):实现实现接口的具体类。
行为实现类及其子类,定义了基本操作和具体实现
java
/**
* 行为实现类(Implementor):定义实现接口,提供基本操作的接口
*/
public interface Implementor {
String watchMovie();
String game();
}
/**
* 行为实现的具体实现类:实现实现接口的具体类。
*/
public class GameImplementor implements Implementor {
@Override
public String watchMovie() {
return "游戏本看电影";
}
@Override
public String game() {
return "游戏本打游戏";
}
}
/**
* 行为实现的具体实现类:实现实现接口的具体类。
*/
public class lightImplementor implements Implementor {
@Override
public String watchMovie() {
return "轻薄本看电影";
}
@Override
public String game() {
return "轻薄本打游戏";
}
}
抽象类及其实现
- 抽象类充当了一个桥梁的作用,抽象类的子类调用和抽象类的方法,抽象类再去调用
行为实现的具体实现类
的方法
java
/**
* 抽象类(Abstraction):定义抽象接口
*/
public abstract class Shape {
Implementor implementor;
public Shape(Implementor implementor) {
this.implementor = implementor;
}
String watchMovie(){
return implementor.watchMovie();
}
String game(){
return implementor.game();
}
}
/**
* 扩展抽象:对抽象的扩展,可以是抽象类的子类或具体实现类。
*/
public class ShapeAsus extends Shape {
public ShapeAsus(Implementor drawCircular) {
super(drawCircular);
}
@Override
String watchMovie() {
return "华硕"+super.watchMovie();
}
@Override
String game() {
return "华硕"+super.game();
}
}
/**
* 扩展抽象:对抽象的扩展,可以是抽象类的子类或具体实现类。
*/
public class ShapeMSI extends Shape{
public ShapeMSI(Implementor implementor) {
super(implementor);
}
@Override
String watchMovie() {
return "微星"+super.watchMovie();
}
@Override
String game() {
return "微星"+super.game();
}
}
测试类
java
public class Client {
public static void main(String[] args) {
Shape shape = new ShapeMSI(new lightImplementor());
System.out.println(shape.watchMovie()+";"+shape.game());
Shape shape1 = new ShapeMSI(new GameImplementor());
System.out.println(shape1.watchMovie()+";"+shape1.game());
Shape shape2 = new ShapeAsus(new GameImplementor());
System.out.println(shape2.watchMovie()+";"+shape2.game());
Shape shape3 = new ShapeAsus(new lightImplementor());
System.out.println(shape3.watchMovie()+";"+shape3.game());
}
}
优点
- 抽象与实现分离:提高了系统的灵活性和可维护性。
- 扩展能力强:可以独立地扩展抽象和实现。
- 实现细节透明:用户不需要了解实现细节。
缺点
- 理解与设计难度:桥接模式增加了系统的理解与设计难度。
- 聚合关联:要求开发者在抽象层进行设计与编程。
装饰器模式
装饰器模式
- 动态的将新功能附加到对象上,再对象功能扩展方面,比继承更加有弹性,也体现了开闭原则(ocp)
- 装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。属于结构型模式
- 装饰器模式提供了一种灵活的替代继承方式来扩展功能
关键代码
- Component接口:定义了可以被装饰的对象的标准(快递的物品)
- ConcreteComponent类:实现Component接口的具体类。
- Decorator抽象类:实现Component接口,并包含一个Component接口的引用。
- ConcreteDecorator类:扩展Decorator类,添加额外的功能。(给快递的物品增加包装,例如泡沫塑料、纸板等)
装饰器模式包含以下几个核心角色:
- 抽象组件(Component):定义了原始对象和装饰器对象的公共接口或抽象类,可以是具体组件类的父类或接口。
- 具体组件(Concrete Component):是被装饰的原始对象,它定义了需要添加新功能的对象。
- 抽象装饰器(Decorator):继承自抽象组件,它包含了一个抽象组件对象,并定义了与抽象组件相同的接口,同时可以通过组合方式持有其他装饰器对象。
- 具体装饰器(Concrete Decorator):实现了抽象装饰器的接口,负责向抽象组件添加新的功能。具体装饰器通常会在调用原始对象的方法之前或之后执行自己的操作。
java
/**
* 可以被装饰的对象
*/
public interface Component {
void pack();
}
/**
* 扩展Decorator类,添加额外的功能
*/
public class ClothesComponent implements Component {
@Override
public void pack() {
System.out.println("打包一件衣服");
}
}
/**
* 实现Component接口的具体类
*/
public class PorcelainComponent implements Component {
@Override
public void pack() {
System.out.println("打包一个瓷器");
}
}
java
/**
* Decorator抽象类:实现Component接口,并包含一个Component接口的引用
*/
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public abstract void extend();
}
/**
* 扩展Decorator类,添加额外的功能
*/
public class ConcreteDecorator1 extends Decorator {
public ConcreteDecorator1(Component component) {
super(component);
}
@Override
public void pack() {
component.pack();
extend();
}
public void extend() {
System.out.println("需要填充塑料泡沫,并且固定在木箱中寄出");
}
}
/**
* 扩展Decorator类,添加额外的功能
*/
public class ConcreteDecorator2 extends Decorator {
public ConcreteDecorator2(Component component) {
super(component);
}
@Override
public void pack() {
component.pack();
extend();
}
public void extend() {
System.out.println("不需要填充物,使用塑料袋寄出即可");
}
}
测试类
java
public class Client {
public static void main(String[] args) {
Decorator decorator =new ConcreteDecorator1(new PorcelainComponent());
decorator.pack();
System.out.println("----------");
Decorator decorator1 =new ConcreteDecorator2(new ClothesComponent());
decorator1.pack();
}
}
结果如下:
优点
- 低耦合:装饰类和被装饰类可以独立变化,互不影响。
- 灵活性:可以动态地添加或撤销功能。
- 替代继承:提供了一种继承之外的扩展对象功能的方式。
缺点
- 复杂性:多层装饰可能导致系统复杂性增加。
使用建议
- 在需要动态扩展功能时,考虑使用装饰器模式。
- 保持装饰者和具体组件的接口一致,以确保灵活性。
- 装饰器模式虽然可以替代继承,但应谨慎使用,避免过度装饰导致系统复杂度过高。
组合模式
组合模式(Composite Pattern)
- 又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
组合模式的核心角色包括:
- 组件(Component):
- 定义了组合中所有对象的通用接口,可以是抽象类或接口。它声明了用于访问和管理子组件的方法,包括添加、删除、获取子组件等。
- 叶子节点(Leaf):
- 表示组合中的叶子节点对象,叶子节点没有子节点。它实现了组件接口的方法,但通常不包含子组件。
- 复合节点(Composite):
- 表示组合中的复合对象,复合节点可以包含子节点,可以是叶子节点,也可以是其他复合节点。它实现了组件接口的方法,包括管理子组件的方法。
- 客户端(Client):
- 通过组件接口与组合结构进行交互,客户端不需要区分叶子节点和复合节点,可以一致地对待整体和部分。
关键代码
- Component接口:定义了所有对象必须实现的操作。
- Leaf类:实现Component接口,代表树中的叶子节点。
- Composite类:也实现Component接口,并包含其他Component对象的集合。
使用场景
- 当要处理的对象可以生成一颗树型结构,如文件系统或组织结构。
- 而我们要对树上的节点和叶子进行操作,能够以一致的方式处理树形结构中的所有对象
java
/**
* 定义了所有对象必须实现的操作
*/
public interface IComponent {
void add(IComponent component);
void remove(IComponent component);
List<IComponent> getList();
}
/**
* 定义了所有对象必须实现的操作
*/
@Data
public abstract class Component implements IComponent{
private String name;
private String des;
public Component(String name, String des) {
this.name = name;
this.des = des;
}
@Override
public void add(IComponent component) {
}
@Override
public void remove(IComponent component) {
}
}
java
/**
* 公司,Composite类:也实现Component接口,并包含其他Component对象的集合。
*/
public class Company extends Component {
private List<IComponent> list = new ArrayList<>();
public Company(String name, String des) {
super(name, des);
}
@Override
public void add(IComponent component) {
list.add(component);
}
@Override
public void remove(IComponent component) {
list.remove(component);
}
@Override
public List getList() {
return list;
}
}
/**
* 部门,Composite类:也实现Component接口,并包含其他Component对象的集合。
*/
public class Department extends Component {
private List<IComponent> list = new ArrayList<>();
public Department(String name, String des) {
super(name, des);
}
@Override
public void add(IComponent component) {
list.add(component);
}
@Override
public void remove(IComponent component) {
list.remove(component);
}
@Override
public List getList() {
return list;
}
}
/**
* 员工,实现Component接口,代表树中的叶子节点,没有子节点,也不需要管理其他节点
*/
public class Staff extends Component {
public Staff(String name, String des) {
super(name, des);
}
@Override
public List getList() {
return null;
}
}
测试类
java
public class Client {
public static void main(String[] args) {
Component company =new Company("某科技公司","提供软件服务");
Component department =new Department("财务部门","管理公司财务");
Component department1 =new Department("技术部门","管理公司财务");
company.add(department);
company.add(department1);
department.add(new Staff("王某某","普通职员"));
department.add(new Staff("李某某","普通职员"));
department.add(new Staff("刘某某","财务主管"));
department1.add(new Staff("黄某某","普通职员"));
department1.add(new Staff("赵某某","普通职员"));
department1.add(new Staff("吴某某","技术主管"));
System.out.println(company);
System.out.println("--------");
List<Component> list = company.getList();
for (Component component : list){
System.out.println(component);
List<Component> list1 = component.getList();
for (Component co : list1){
System.out.println(co);
}
System.out.println("--------");
}
}
}
执行结果
优点
- 简化客户端代码:客户端可以统一处理所有类型的节点。
- 易于扩展:可以轻松添加新的叶子类型或树枝类型。
- 适用于需要处理复杂树形结构的场景,如文件系统、组织结构等。
缺点:
- 需要较高的抽象性,如果节点和叶子有很多差异,就不适用于组合模式
外观模式
外观模式(Facade Pattern)
- 也叫过程模式,属于结构型模式。
- 外观模式为子系统中一组接口提供了一个一致性界面,来隐藏系统的复杂性,客户端只需要和这个接口发生关系即可,无需关心子系统的内部实现,类似于公司前台
- 简化了客户端对复杂系统的操作,隐藏内部实现细节,降低了客户端与复杂子系统之间的耦合度。
结构
- 外观(Facade):
- 提供一个简化的接口,封装了系统的复杂性。外观模式的客户端通过与外观对象交互,而无需直接与系统的各个组件打交道。
- 子系统(Subsystem):
- 由多个相互关联的类组成,负责系统的具体功能。外观对象通过调用这些子系统来完成客户端的请求。
- 客户端(Client):
- 使用外观对象来与系统交互,而不需要了解系统内部的具体实现。
优点
- 减少依赖:客户端与子系统之间的依赖减少。
- 提高灵活性:子系统的内部变化不会影响客户端。
- 增强安全性:隐藏了子系统的内部实现,只暴露必要的操作。
缺点
- 违反开闭原则:对子系统的修改可能需要对外观类进行相应的修改。
使用建议
- 在需要简化复杂系统访问时使用外观模式。
- 确保外观类提供的方法足够简单,以便于客户端使用。
- 避免过度使用外观模式,以免隐藏过多的细节,导致维护困难,如果子系统比较简单就不要使用外观模式,增加系统复杂度
- 在维护一个遗留的大型项目时,这个系统可能已经变的难以维护和扩展,可以考虑使用外观模式开发一个外观类,来提供系统遗留的比较清晰的接口与新系统进行交互,提高复用性
子系统:
java
/**
* 后勤
*/
public class Logistics {
public void receive() {
System.out.println("在后勤部,领取工牌");
}
public void restitution(){
System.out.println("在后勤部,归还工牌");
}
}
/**
* 设备部
*/
public class Equipment {
public void receive(){
System.out.println("在设备部,领取电脑");
}
public void still(){
System.out.println("在设备部,归还电脑");
}
}
/**
* 网络部
*/
public class Network {
public void open(){
System.out.println("在网络部,开通网络");
}
public void close(){
System.out.println("在网络部,关闭网络");
}
}
外观
java
/**
* 外观类
*/
@Data
@AllArgsConstructor
public class Appearance {
private Equipment equipment;
private Logistics logistics;
private Network network;
public void employment(){
System.out.println("办理入职手续");
logistics.receive();
equipment.receive();
network.open();
}
public void quit(){
System.out.println("办理离职手续");
equipment.still();
network.close();
logistics.restitution();
}
}
客户端
java
public class Client {
public static void main(String[] args) {
Appearance appearance = new Appearance(new Equipment(), new Logistics(), new Network());
appearance.employment();
System.out.println("----------------");
appearance.quit();
}
}
享元模式
享元模式(Flyweight Pattern)
- 又称蝇量模式,属于结构型模式(享:共享,元:对象)
- 运用共享技术有效地支持大量细粒度对象,可以减少创建对象的数量,以减少内存占用和提高性能。
- 经典应用场景为:String常量池、数据库连接池、缓冲池等等
结构
- 享元工厂(Flyweight Factory):
- 负责创建和管理享元对象,通常包含一个池(缓存)用于存储和复用已经创建的享元对象。
- 具体享元(Concrete Flyweight):
- 实现了抽象享元接口,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态则由客户端传递。
- 抽象享元(Flyweight):
- 定义了具体享元和非共享享元的接口,通常包含了设置外部状态的方法。
- 客户端(Client):
- 使用享元工厂获取享元对象,并通过设置外部状态来操作享元对象。客户端通常不需要关心享元对象的具体实现。
享元模式的内部状态和外部状态
- 内部状态指对象共享出来的信息,也就是存储在对象内部且不会随着环境改变的信息
- 外部状态指会随着环境的改变而改变的、不可共享的信息
- 以活字硬刷术为例,汉字本身是不可变的内部状态,汉字的字体是可变的外部状态
优点
- 减少内存消耗:通过共享对象,减少了内存中对象的数量。
- 提高效率:减少了对象创建的时间,提高了系统效率。
缺点
- 增加系统复杂度:需要分离内部状态和外部状态,增加了设计和实现的复杂性。
- 线程安全问题:如果外部状态处理不当,可能会引起线程安全问题。
使用建议
- 在创建大量相似对象时考虑使用享元模式。
- 确保享元对象的内部状态是共享的,而外部状态是独立于对象的。
- 要明确区分内部状态和外部状态,避免混淆。
- 使用享元工厂来控制对象的创建和复用,确保对象的一致性和完整性。
java
/**
* 抽象享元,定义了具体享元和非共享享元的接口,通常包含了设置外部状态的方法。
*/
public interface Flyweight {
public String test(String type);
}
/**
* 具体享元,实现了抽象享元接口,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态则由客户端传递。
*/
@Data
@AllArgsConstructor
public class ConcreteFlyweight implements Flyweight {
// 具体的汉字,内部状态
private String value;
// 字体为外部状态
@Override
public String test(String type) {
return type + ":" + value;
}
}
java
/**
* 享元工厂:负责创建和管理享元对象,通常包含一个池(缓存)用于存储和复用已经创建的享元对象。
*/
public class Factory {
private Map<String, Flyweight> map = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (!map.containsKey(key)) {
Flyweight flyweight = new ConcreteFlyweight(key);
map.put(key, flyweight);
}
return map.get(key);
}
// 获取集合元素个数
public Integer size() {
return map.size();
}
}
测试类
java
public class Client {
public static void main(String[] args) {
Factory factory = new Factory();
Random random =new Random();
StringBuilder sb =new StringBuilder();
StringBuilder sb1 =new StringBuilder();
for (int a = 0; a < 10; a++) {
int in = random.nextInt(1000)+20000;
Character in1 = (char) in;
Flyweight flyweight = factory.getFlyweight(in1.toString());
sb.append(flyweight.test("楷体")).append(";");
sb1.append(flyweight.test("宋体")).append(";");
}
System.out.println(sb);
System.out.println(sb1);
System.out.println(factory.size());
}
}
只生成了10个汉字对象
Integer 中使用到了享元模式,Integer 中有一个缓存池范围为:-128到127
代理模式
代理模式(Proxy Pattern)
- 属于结构型模式
- 代理模式通过引入一个代理对象来控制对原对象的访问,用一个类代表另一个类的功能
- 代理对象在客户端和目标对象之间充当中介,负责将客户端的请求转发给目标对象,同时可以在转发请求前后进行额外的处理。
- 代理模式解决的是在直接访问某些对象时可能遇到的问题,例如对象创建成本高、需要安全控制或远程访问等。
- 代理模式主要有三种:静态代理、动态代理(JDK代理)、cglib代理(可以在内存中动态的创建对象,而不需要实现接口,cglib代理也属于动态代理)
结构
- 抽象主题(Subject):
- 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题。
- 真实主题(Real Subject):
- 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问。
- 代理(Proxy):
- 实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能,例如延迟加载、权限控制、日志记录等。
- 客户端(Client):
- 使用抽象主题接口来操作真实主题或代理主题,不需要知道具体是哪一个实现类。
注意事项
- 与适配器模式的区别:适配器模式改变接口,而代理模式不改变接口。
- 与装饰器模式的区别:装饰器模式用于增强功能,代理模式用于控制访问。
优点
- 职责分离:代理模式将访问控制与业务逻辑分离。
- 扩展性:可以灵活地添加额外的功能或控制。
- 智能化:可以智能地处理访问请求,如延迟加载、缓存等。
缺点
- 性能开销:增加了代理层可能会影响请求的处理速度。
- 实现复杂性:某些类型的代理模式实现起来可能较为复杂。
静态代理
静态代理
-
需要定义接口或者父类,被代理的对象需要与代理对象一起实现相同的接口或者继承相同的父类
-
优点是:可以在不修改目标对象的基础上,对功能进行扩展
-
缺点是:
- 因为代理对象需要和目标对象实现同样的接口,所以会有很多代理类
- 而且接口增加方法,目标对象和代理对象都需要维护
java
/**
* 公司:抽象主题(Subject):
* 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题
*/
public interface Company {
// 发货
void deliverGoods();
}
/**
* 仓库:真实主题(Real Subject):
* 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问
*/
public class Warehouse implements Company {
@Override
public void deliverGoods() {
System.out.println("货物从仓库发出");
}
}
/**
* 销售:代理(Proxy):
* 实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能
*/
@Data
@AllArgsConstructor
public class Sale implements Company{
// 被代理对象
private Warehouse warehouse;
@Override
public void deliverGoods() {
// 可以实现一些额外的功能
System.out.println("客户从销售经理订购货物");
warehouse.deliverGoods();
System.out.println("发货完成");
}
}
测试类
java
public class Client {
public static void main(String[] args) {
Company company =new Sale(new Warehouse());
company.deliverGoods();
}
}
调用的代理对象的方法,最终执行的是被代理对象的方法
动态代理
动态代理
- 代理对象不需要实现接口,但是目标对象要实现接口,否则不能使用动态代理
- 代理对象的生成利用了JDK的API,动态的在内存中构建对象,所以动态代理也叫做:JDK代理、接口代理
java
/**
* 动态代理
*/
@Data
@AllArgsConstructor
public class DynamicProxy {
// 维护一个目标对象
private Object target;
public Object getProxyInstance(){
/**
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
* loader 是类加载器
* interfaces 目标对象实现的接口类型
* h 事件处理,执行目标对象的方法时,会触发事件处理器方法
* InvocationHandler 是函数式接口,可以使用lambda表达式简化
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("jdk动态代理开始");
// 调用目标方法
Object invoke = method.invoke(target, args);
System.out.println("jdk动态代理结束");
return invoke;
});
}
}
java
/**
* 公司:抽象主题(Subject):
* 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题
*/
public interface Company {
// 发货
void deliverGoods();
}
/**
* 仓库:真实主题(Real Subject):
* 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问
*/
public class Warehouse implements Company {
@Override
public void deliverGoods() {
System.out.println("货物从仓库发出");
}
}
测试类
java
public class Client {
public static void main(String[] args) {
Company proxyInstance =(Company) new DynamicProxy(new Warehouse()).getProxyInstance();
proxyInstance.deliverGoods();
}
}
cglib代理
cglib代理
- 静态代理和JDK代理都要求目标对象实现一个接口,但是有时候目标对象只是一个单独的类,并没有实现任何接口,这个时候可以使用目标对象子类来实现代理,也就是cglib代理
- cglib代理也就叫做子类代理,原理是在内存中构建一个目标对象的子类对象从而实现对目标对象功能的扩展,也算是动态代理的一种
- Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用,例如 SpringAOP,实现方法拦截
- Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
- Cglib 代理因为需要生成目标对象的子类,所以被代理的类不能是final类;并且目标方法如果是final或者静态的,就不会被拦截所以无法扩展
SpringAOP
- 目标对象需要实现接口,用 JDK 代理
- 目标对象不需要实现接口,用 Cglib 代理
使用cglib,需要引入对应的包:
xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
示例代码
java
import lombok.AllArgsConstructor;
import lombok.Data;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
@Data
@AllArgsConstructor
public class CglibProxy implements MethodInterceptor {
// 目标对象
private Object target;
// 返回一个代理对象,是目标对象 target 的子类对象
public Object getInstance(){
// 创建工具类
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(target.getClass());
// 设置回调函数为自己
enhancer.setCallback(this);
// 创建并返回子类对象
return enhancer.create();
}
/**
* @param o: 代理对象
* @param method: 被代理方法
* @param objects: 方法入参
* @param methodProxy: CGLIB方法
**/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理开始");
Object invoke = method.invoke(target, objects);
System.out.println("cglib代理结束");
return invoke;
}
}
java
/**
* 仓库:真实主题(Real Subject):
*/
public class Warehouse {
public void deliverGoods() {
System.out.println("货物从仓库发出");
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
Company proxyInstance =(Company) new DynamicProxy(new Warehouse()).getProxyInstance();
proxyInstance.deliverGoods();
}
}
如果使用的 Java 9以上的 JDK会出现以下报错:
java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @722c41f4
因为自 Java 9 开始引入,它强制执行封装边界,并且只允许明确导出的包进行访问。默认情况下,java.lang和其他一些核心Java包都不会向unnamed模块开放。
解决方法:
-
如果你正在编写一个需要使用反射来访问java.lang中类或接口的应用程序,你需要将你的应用程序打包为一个模块,并在模块声明中明确导出你需要反射访问的包。
-
如果你正在编写一个Java应用程序,并且需要使用反射来访问java.lang中的类或接口,你可以在你的module-info.java文件中添加以下代码:
--add-opens java.base/java.lang=ALL-UNNAMED