一、简介
Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题,
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。
Java中使用代理技术主要用于扩展原功能又不侵入(修改)源代码。
主要应用: 比如想在某个类的某个方法执行之前打印日志或者记录下开始时间,但是又不好将打印日志和时间的逻辑写入原来的方法里。这时就可以创建一个代理类实现和原方法相同的方法,通过让代理类持有真实对象,然后代码调用的时候直接调用代理类的方法,来达到增强业务逻辑的目的。
1、代理分类:
静态代理: 由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
Java中的静态代理要求代理类(ProxySubject)和委托类(RealSubject)都实现同一个接口(Subject)。静态代理中代理类在编译期就已经确定,而动态代理则是JVM运行时动态生成,静态代理的效率相对动态代理来说相对高一些,但是静态代理代码冗余大,一旦需要修改接口,代理类和委托类都需要修改。
动态代理: 在程序运行时运用反射机制动态创建而成。
Java中的动态代理依靠反射来实现,代理类和委托类不需要实现同一个接口。委托类需要实现接口,否则无法创建动态代理。代理类在JVM运行时动态生成,而不是编译期就能确定。
Java动态代理主要涉及到两个类:java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
。代理类需要实现InvocationHandler接口或者创建匿名内部类,而Proxy用于创建动态动态。
2、代码区别:
静态代理:
- 接口类。
- 具体用户管理实现类。
- 代理类(业务增强类)
- 客户端调用。
动态代理:(少用户自己实现类)
- 接口类。
- 代理类(业务增强类)
- 客户端调用。
二、静态代理
1、接口定义要做的事情
public interface IBuyHouse {
// 定义要做的事情
public void buyHouse();
}
2、具体用户实现类
/**
* 用户实现类(也就是自己做,需要干的事情)
*/
public class HouseDelegation implements IBuyHouse{
@Override
public void buyHouse() {
System.out.println("去选房,购房.");
}
}
3、代理类(业务增强类)
/**
* 代理类(代替你完成工作,可以附加功能)
*/
public class HouseAgent implements IBuyHouse{
private IBuyHouse iBuyHouse;
public HouseAgent(IBuyHouse iBuyHouse) {
this.iBuyHouse = iBuyHouse;
}
@Override
public void buyHouse() {
// 1、代替你完成工作
iBuyHouse.buyHouse();
// 2、代理类新增的功能
System.out.println("倒卖你的信息");
}
}
4、测试类
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
HouseDelegation delegation = new HouseDelegation();
// 1、将你要做的事情,传递给代理对象
HouseAgent agent = new HouseAgent(delegation);
// 2、执行代理类方法
agent.buyHouse();
}
}
优点:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,newUserManagerImpl()可以应用工厂将它隐藏,如上只是举个例子而已。
缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。
通俗点的解释:
优点:
1、实现解耦,不需要知道怎么做,只知道代理即可。
缺点:
1、大量的重复代码,改代码,需要维护的量大。
2、每个类都写代码,程序太大时,无法实现。
三、动态代理
使用动态代理,我们最大的改变就是不需要定义一个个的代理类了。最重要的是获取到代理对象,有了代理对象,我们就可以直接调用代理对象了。
1、JDK动态代理类
JDK动态代理不仅可以代理有接口有实现类的情况,也可以代理只有接口没有实现类的情况。
使用JDK动态代理无需引入任何外部的jar包,JDK已经给我们提供了一种获取代理对象的API,只需要我们传入相关信息,它就可以返回我们需要的代理对象。
java.lang.reflect.Proxy类的定义如下:
//CLassLoader loader:类的加载器
//Class<?> interfaces:得到全部的接口(代理的全部接口)
//InvocationHandler h:得到InvocationHandler接口的子类的实例(增强业务逻辑的,也就是说增加额外功能)
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
java.lang.reflect.InvocationHandler接口的定义如下:
//Object proxy:被代理的对象
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
1.1、有接口有委托类的情况
1、接口定义要做的事情
public interface IBuyHouse {
// 定义要做的事情
public void buyHouse();
}
2、具体用户实现类
/**
* 用户实现类(也就是自己做,需要干的事情)
*/
public class HouseDelegation implements IBuyHouse{
@Override
public void buyHouse() {
System.out.println("去选房,购房.");
}
}
3、代理类(业务增强类)
package com.lydms.testPro;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 功能增强类
*/
public class MyProxyPlus implements InvocationHandler {
// 把委托对象传递进来进行增强
private Object object;
public MyProxyPlus(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1、执行原来的业务逻辑
Object result = method.invoke(object, args);
// 2、执行增强逻辑
System.out.println("对原有的功能进行增强");
return result; // 如果原有业务逻辑有返回值别忘了返回
}
}
4、测试类
import java.lang.reflect.Proxy;
/**
* 测试类
*/
public class test {
public static void main(String[] args) {
// 获取IBuyHouse的代理对象
HouseDelegation houseDelegation = new HouseDelegation();
// 1、将你要做的事情,传递给代理对象(并做功能增强MyProxyPlus)
IBuyHouse iBuyHouse = (IBuyHouse) Proxy.newProxyInstance(houseDelegation.getClass().getClassLoader(),
houseDelegation.getClass().getInterfaces(), new MyProxyPlus(houseDelegation));
// 2、执行代理类方法
iBuyHouse.buyHouse();
}
}
总结:
同样进行了增强,是不是代理类不见了呢!!!!这就是动态代理的好处,不需要你定义代理类了,你只需要能拿到代理对象就可以
1.2、仅有接口的情况
假如说上面我们只定义了IBuyCar接口和IBuyHouse接口,没有委托类(实现类),也是可以玩的。定义一个InvocationHandler接口的实现,用于写业务逻辑,你把所有的业务逻辑写在invoke方法中就行了
1、接口定义要做的事情
public interface IBuyHouse {
// 定义要做的事情
public void buyHouse();
}
2、代理类(业务增强类)
/**
* 功能增强类
*/
public class MyProxyPlus implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("对原有的功能进行增强");
return null;
}
}
3、测试类
import java.lang.reflect.Proxy;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
// 1、将你要做的事情,传递给代理对象(并做功能增强MyProxyPlus)
IBuyHouse iBuyHouse = (IBuyHouse) Proxy.newProxyInstance(Test.class.getClassLoader(),
new Class[]{IBuyHouse.class}, new MyProxyPlus());
// 2、执行代理类方法
iBuyHouse.buyHouse();
}
}
总结:
业务逻辑从无到有不也是一种增强嘛!是不是代理类不见了而且连实现类都不需要了呢!!!!这就是我们Mapper动态代理的底层原理(只要定义接口,不需要写实现类)
2、CGLIB动态代理
Java中的动态代理包括JDK动态代理和CGLIB动态代理。使用这两种代理方式我们都可以不用定义代理类,区别在于使用JDK动态代理必须有一个接口类,使用CGLIB动态代理不需要接口类。
所以如果你要对一个实现了接口的类进行业务增强就用JDK动态代理,如果就对一个普通类进行业务增强就用CGLIB动态代理。如下
1、cglib是第三方jar,因此需要引入jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
2、具体用户实现类
public class Book {
public void addBook() {
System.out.println("新增图书...");
}
}
3、测试类
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
Book book = new Book();
// 1、获取book对象的代理对象,
Book bookProxy = (Book) Enhancer.create(book.getClass(), new MethodInterceptor() {
Object obj = null;
// 2、都是对业务逻辑的增强
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("日志开始........................");
obj = method.invoke(book, objects);
System.out.println("日志结束........................");
return obj;
}
});
// 3、执行方法
bookProxy.addBook();
}
}
结果:
总结:
cglib动态代理其实就是把原有对象传进去进行方法拦截,拦截到之后进行逻辑增强
三、总结
- 使用代理技术就是为了帮我们在不入侵原有代码的情况下增强业务逻辑。
- 你完全可以使用静态代理一个一个去定义代理类,但是这样的话太过于繁琐,而且有些情况下你不知道未来会有什么接口(比如咱们的Mybatis,你现在有个UserMapper.java,以后还可能有更多其他的Mapper接口,这些都是不确定的),所以最好采用动态代理生成代理对象吧。
- 有接口就用JDK动态代理。