代理模式是什么?
代理模式也称为委托者模式,属于结构型设计模式,代理模式通常是提供一个中间人,用于解决两类问题:
- 控制对目标对象的访问
- 在访问目标对象时增加额外功能
‼️本质是通常是提供一个中间人,以控制目标对象的访问。
什么是静态代理?
静态代理是指代理关系在编译期确定的代理模式。使用静态代理时,通常的做法是为每个业务类抽象一个接口,对应地创建一个代理类。
1、定义基础接口
public interface HttpApi {
String get(String url);
}
2、网络请求的真正实现
public class RealModule implements HttpApi {
@Override
public String get(String url) {
return "result";
}
}
3、代理类
public class Proxy implements HttpApi {
private HttpApi target;
Proxy(HttpApi target) {
this.target = target;
}
@Override
public String get(String url) {
// 扩展的功能
Log.i("http-statistic", url);
// 访问基础对象
return target.get(url);
}
}
可以看出,当需要频繁使用代理模式时,我们需要创建很多代理类,需要创建很多重复的代码。
java 动态代理
java 的动态代理是 jdk 1.3 中引入的特性,“动态”是相对于“静态”代理而言,代理类不是在编译期生成的,而是在运行时生成。 java 动态代理的核心 API 是 Proxy
类和InvocationHandler
接口。
**InvocationHandler**
:创建动态代理示例必须提供代理处理器
,代理处理器
是实现了InvocationHandler
接口的类的实例,这个接口只有一个invoke
方法,调用代理对象的方法实际上会传递给invoke
方法,方法签名如下:Object invoke(Object proxy, Method method, Object[] args)
**Proxy**
: 创建代理对象需要使用 Proxy 类的newProxyInstance
方法,方法接受参数如下:ClassLoader loader
:类加载器通常就是接口类的ClassLoaderClass<?>[] interfaces
:代理类实现的接口数组,InvocationHandler h
:调用处理器实例
我们来看一个简单的例子:
import org.junit.jupiter.api.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口
interface hello {
void hi();
}
// 原对象
class helloOrigin implements hello {
@Override
public void hi() {
System.out.println("hi!");
}
}
// 调用处理器
class proxyHandler implements InvocationHandler {
hello target;
public proxyHandler(hello target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy !");
return method.invoke(target, args);
}
}
public class DynimicProxyTest {
@Test
public void test() {
// 创建原对象类
hello originBean = new helloOrigin();
originBean.hi();
// 创建代理类
hello proxyBean = (hello) Proxy.newProxyInstance(hello.class.getClassLoader(),
new Class[]{hello.class},
new proxyHandler(originBean));
proxyBean.hi();
}
}
💡注意:只有接口,没有实现类的情况下, java 动态代理也可以直接在运行期创建接口对象:
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
interface Hello {
void morning(String name);
}
并不存在可以直接实例化接口的黑魔法,动态代理其实就是JVM帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),把上面的动态代理改写为静态实现类大概长这样:
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning", String.class),
new Object[] { name });
}
}
CGLIB 动态代理
java 动态代理要求代理类必须实现某个接口,无法代理没有实现接口的类,存在一定的局限。 CGLIB
是一个基于字节码的代码生成库,可以用来生成动态代理对象,与Java标准库中的动态代理相比,CGLIB动态代理所生成的代理对象是基于继承的方式,而不是基于接口的,所以也叫子类代理。很多流行的框架如Hiberate
、Spring
等很多框架都使用CGLIB
库。 maven 中引入CGLIB
:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
首先先创建一个被代理的目标类:(注意目标代理类和目标代理方法不能是被 final
修饰的,因为 CGLIB
动态代理是通过生成目标类的子类来实现代理,而 final
修饰的方法无法被重写)。
public class PersonService {
public String sayHello(String name) {
return "Hello " + name;
}
public Integer lengthOfName(String name) {
return name.length();
}
}
接着我们使用cglib
的Enhancer
类创建一个代理:
Enhancer enhancer = new Enhancer();
// 设置目标类,可以是类或接口
enhancer.setSuperclass(PersonService.class);
// 设置拦截方法
enhancer.setCallback((FixedValue) () -> "Hello Tom!");
// 创建代理类
PersonService personService = (PersonService) enhancer.create();
System.out.println(personService.sayHello("lm"));
// 打印
// "Hello Tom!"
使用setSuperclass
方法指定被代理类; 使用setCallback
指定回调方法,当调用代理类的目标方法时会交由回调方法执行,上边代码例子使用FixedValue
进行强制类型转换,表示回调方法直接返回一个值。 我们还可以使用setCallback
指定更复杂的回调方法,比如说根据不同的被拦截的方法,采取不同的处理策略:
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
if (method.getName().equals("sayHello")) {
return "Hello Tom!";
} else {
return proxy.invokeSuper(obj, args);
}
});
上边例子中,我们拦截了目标类PersonService
中sayHello
方法, 当调用sayHello
方法时直接返回"Hello Tom!"。注意MethodInterceptor
是用于进行类型转换的。