一、代理模式
1、代理模式
目标对象不可访问,通过代理对象增强功能访问
生活中
-- 房东 ===> 目标对象
-- 房屋中介 ===> 代理对象
-- 你,我 ===> 客户端对象
-- 服装厂 ===> 目标对象
-- 门店 ===> 代理对象
-- 你,我 ===> 客户端对象
开发中
-- 运营商(电信、移动、联通) ===> 目标对象
-- 第三方公司 ===> 代理对象
-- 开发的应用程序需要发送短信的功能 ===> 客户端对象
2、代理模式的作用
- 控制目标对象的访问
- 增强功能
3、代理模式的分类
- 静态代理
- 动态代理
- JDK 动态代理
- CGLib 动态代理(子类代理)
二、静态代理
1、特点
- 目标对象和代理对象实现同一个业务接口
- 目标对象必须实现接口
- 代理对象在程序运行前就已经存在
- 能够灵活的进行目标对象的切换,却无法进行功能的灵活处理(使用动态代理解决此问题)
2、静态代理执行流程
3、代码实现
业务接口
/*** 业务接口 */
public interface Service {
// 规定唱歌的业务功能
void sing();
}
代理对象
/*** 代理对象,处理除唱歌主业务除外的其他业务 */
public class Agent implements Service {
public void sing() {
System.out.println("预定时间……");
System.out.println("预定场地……");
// 业务功能必须由目标对象实现
SuperStarLiu superStarLiu = new SuperStarLiu();
superStarLiu.sing();
System.out.println("结算费用……");
}
}
目标对象
/*** 目标对象:实现业务接口的功能 */
public class SuperStarLiu implements Service {
public void sing() {
System.out.println("我是刘德华,我正在唱歌……");
}
}
客户端对象
public class MyTest {
@Test public void testAgent(){
// 测试功能
/*
SuperStarLiu superStarLiu = new SuperStarLiu();
superStarLiu.sing(); // 我是刘德华,我正在唱歌……
*/
/*
预定时间……
预定场地……
我是刘德华,我正在唱歌……
结算费用……
Agent agent = new Agent();
agent.sing();
*/
// 有接口和实现类,必须使用接口指向实现类(规范)
/*
预定时间……
预定场地……
我是刘德华,我正在唱歌…… 结算费用……
*/
Service agent = new Agent();
agent.sing();
}
}
4、面向接口编程
- 类中成员变量设计为接口
- 方法的形参设计为接口
- 方法的返回值设计为接口,调用时接口指向实现类
5、灵活切换目标对象
public class Agent implements Service {
// 类中的成员变量设计为接口
public Service target;
// 目标对象
// 传入目标对象,方法的参数设计为接口
public Agent(Service target){
this.target = target;
}
public void sing() {
System.out.println("预定时间……");
System.out.println("预定场地……");
// 业务功能必须由目标对象实现(写死了)
/*
SuperStarLiu superStarLiu = new SuperStarLiu();
superStarLiu.sing();
SuperStarZhou superStarZhou = new SuperStarZhou();
superStarZhou.sing();
*/
// 面向接口编程,调用时接口指向实现类 -- 【向上转型】
target.sing();
System.out.println("结算费用……");
}
}
@Test public void testAgent(){
Service agent1 = new Agent(new SuperStarLiu());
agent1.sing();
Service agent2 = new Agent(new SuperStarZhou());
agent2.sing();
}
6、静态代理的弊端
当业务接口的功能变化,那么所有实现类都要修改
三、动态代理
1、动态代理
代理对象在程序运行的过程中动态在内存构建,可以灵活的进行业务功能的切换
2、JDK 动态代理
- 目标对象必须实现业务接口
- 代理对象不需要实现业务接口
- 动态代理的对象在程序运行前不存在,在程序运行时动态在内存中构建
- 动态代理灵活的进行业务功能的切换
- 本类中特有的方法(非接口中的方法)不能被代理
3、JDK 动态代理
使用现成的工具类完成 JDK 动态代理
1)Proxy 类
包:java.lang.reflect.Proxy
方法:Proxy.newProxyInstance()
作用:动态生成代理对象
public static Object newProxyInstance(ClassLoader loader, // 类加载器
Class<?>[] interfaces, // 目标对象实现的所有接口
InvocationHandler h // 代理对象)
throws IllegalArgumentException{……}
2)Method 类
作用:反射用的类,用来进行目标对象的方法的反射调用
3)InvocationHandler 接口
作用:实现代理和业务功能的,调用时使用匿名内部实现
4)代码实现
ClassLoader loader // 类加载器,完成目标对象的加载
Class<?>[] interfaces // 目标对象实现的所有接口
InvocationHandler h // 代理对象
业务接口
package com.qiuxuan.service;
/**
* 业务接口
*/
public interface Service {
void sing();
String show(int age);
}
目标对象
package com.qiuxuan.service.impl;
import com.qiuxuan.service.Service;
/**
* 目标对象
*/
public class SuperStarLiu implements Service {
@Override
public void sing() {
System.out.println("我是刘德华,我在演唱歌曲…………");
}
@Override public String show(int age) {
System.out.println("刘德华show……" + age);
return "liu";
}
// 此方法不能被代理(不能增强功能)
public void one(){
System.out.println("one ..........");
}
}
package com.qiuxuan.service.impl;
import com.qiuxuan.service.Service;
/**
* 目标对象
*/
public class SuperStarZhou implements Service {
@Override
public void sing() {
System.out.println("我是周润发,我正在唱歌…………");
}
@Override
public String show(int age) {
System.out.println("周润发show……" + age);
return "zhou";
}
}
代理工厂
package com.qiuxuan.proxy;
import com.qiuxuan.service.Service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
// 类中的成员变量设计为接口
Service target;
// 传入目标对象,参数设计为接口
public ProxyFactory(Service target){
this.target = target;
}
// 返回动态代理对象
public Object getAgent(){
return Proxy.newProxyInstance(
// ClassLoader loader 类加载器,完成目标对象的加载
target.getClass().getClassLoader(),
// Class<?>[] interfaces 目标对象实现的所有接口
target.getClass().getInterfaces(),
// InvocationHandler h 实现代理功能的接口,传入匿名内部实现
new InvocationHandler() {
/**
* @param proxy 创建代理对象
* @param method 目标方法
* @param args 目标方法的参数
* @return obj 目标方法的返回值
*/
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
// 代理功能
System.out.println("预定时间……");
// 代理功能
System.out.println("预定场地……");
// 主业务功能
// target.sing(); 这样还是写死了
// invoke() -- 调用 target 对象的方法
Object obj = method.invoke(target,args);
// 代理功能
System.out.println("结算费用……");
return obj;
}
}
);
}
}
客户端对象(测试类)
package test;
import com.qiuxuan.proxy.ProxyFactory;
import com.qiuxuan.service.Service;
import com.qiuxuan.service.impl.SuperStarLiu;
import org.junit.jupiter.api.Test;
public class MyTest {
@Test
public void testJDK(){
// 获取工厂对象
ProxyFactory factory = new ProxyFactory(new SuperStarLiu());
// 调用工厂对象的 getAgent() 方法动态获取代理对象
// Object agent = factory.getAgent();
Service agent = (Service)factory.getAgent();
// 代理对象匿名内部实现了接口的所有方法,所以在调用 agent 的 sing() 方法时实际执行的是代理对象 sing() 方法的具体实现
agent.sing();
agent.show(20);
System.out.println(agent.getClass()); // class com.sun.proxy.$Proxy9
Service service = new SuperStarLiu();
System.out.println(service.getClass());
// class com.qiuxuan.service.impl.SuperStarLiu
}
}
4、CGLib 动态代理
又称为“子类代理”,通过动态在内存中构建子类对象,重写父类的方法进行代理功能的增强
如果目标对象没有实现接口,则只能通过 CGLib 子类代理来进行功能增强
子类代理是通过对象字节码框架 ASM 来实现的
简版 CGLib 代理实现
package org.example;
/**
* 目标对象,不用实现接口
*/
public class SuperStarLiu {
public void sing(){
System.out.println("我是刘德华,我正在唱歌…………");
}
}
package org.example;
/**
* @Author: qiuxuan
* @Date: Create in 13:10 2022-06-15
*/
public class SubSuperStarLiu extends SuperStarLiu{
// 重写父类方法,进行增强功能
@Override
public void sing() {
// 子类完成代理功能
System.out.println("预定时间…………");
// 子类完成代理功能
System.out.println("预定场地…………");
// 父类实现自己的功能
super.sing();
// 子类完成代理功能
System.out.println("结算费用…………");
}
}
package org.example.test;
import org.example.SubSuperStarLiu;
import org.example.SuperStarLiu;
import org.junit.Test;
/**
* @Author: qiuxuan
* @Date: Create in 13:14 2022-06-15
*/
public class MyTest {
@Test
public void test(){
SuperStarLiu liu = new SubSuperStarLiu();
liu.sing();
}
}
/*
预定时间…………
预定场地…………
我是刘德华,我正在唱歌…………
结算费用…………
*/
5、CGLib 动态代理的特点
JDK 的动态代理有一个限制,就是使用动态代理的目标对象必须实现一个或多个接口。如果向代理没有实现接口的类,就可以使用 CGLIB 实现。
CGLIB 是一个强大的高性能的代码生成包,它可以在运行期扩展 Java 类与实现 Java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP 和 dynaop,为他们提供方法的 interception
CGLIB 包的底层是通过使用一个小而块的字节码处理框架 ASM,来转换字节码并生成新的类。不建议直接使用 ASM,因为它要求你必须对 JVM 内部结构包括 class 文件的格式和指令集都很熟悉。
6、CGLib 实现步骤
引入 cglib-jar 文件,但是 spring 的核心包中已经包括了 cglib 功能,所以直接引入 spring-cglib-5.2.5.jar 即可
引入功能包后,就可以在内存中动态构建子类
被代理的类不能为 final,否则报错
目标对象的方法如果为 final / staitic ,那么就不会被拦截,即不会执行目标对象额外的业务方法
代码实现结构
public class ProxyFactory implements MethodInterceptor{
// 目标对象
private Object target;
// 传入目标对象
public ProxyFactory(Object target){
this.target = target;
}
// CGLib 采用底层的字节码技术在子类中采用方法拦截的技术,拦截父类指定方法的调用,并顺势植入代理功能的代码
@Override
public Object intercept(Object obj,Method method,MethodProxy proxy) throws Throwable{
// 代理对象的功能
System.out.println("预定场地…………");
// 调用目标对象的方法
Object returnValue = method.invoke(target,arg2);
// 代理对象的功能
System.out.println("结账走人…………");
return returnValue;
}
// 生成代理对象
public Object getProxyInstance(){
// 使用工具类
Enhancer en = new Enhancer();
// 设置父类
en.setSuperClass(target.getClass());
// 设置回调函数
em.setCallback(this); // 调用 intercept()
// 创建子类(代理)对象
return en.create();
}
}
@Test
public void testCglibProxy(){
SuperStar superStar = new SuperStar();
System.out.println(superStar.getClass());
SuperStar proxy = (SuperStar) new ProxyFactory(superStar).getProxyInstance();
System.out.println(proxy.getClass());
proxy.sing();
}
一 叶 知 秋,奥 妙 玄 心