动态代理
特点:代理类是在程序运行期间由 JVM 通过反射等机制动态的生成的,字节码随用随创建,随用随加载,即代理类及代理对象不用我们自己创建
作用:不修改源码的基础上对已有方法进行增强
代理模式的职责:把不是真实对象该做的事情从真实对象中移除—职责分离。
基于接口的动态代理
涉及的类:Proxy
创建代理对象:Proxy.newProxyInstance()
创建代理对象的要求:被代理类最少需要实现一个接口,否则不能使用
newProxyInstance方法的参数:
-
ClassLoader:类加载器
它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器 -
Class[]:字节码数组
它是用于让代理对象和被代理对象有相同方法
-
InvocationHandler:用于提供增强的代码
写具体需要增强的代码,通常情况下都是匿名内部类,但不是必须的
在该匿名内部类中需要实现一个invoke方法
作用:执行被代理对象的任何接口方法都会经过该方法
@param proxy 代理对象的引用
@param method 当前执行的方法
@param args 当前执行方法的参数
@return 返回一个代理对象
然后通过代理对象来调用实现类中的方法
例子:
/*
*1.代理对象和真实对象实现相同的接口
*2. 代理对象 = Proxy.newProxyInstance()
*3.使用代理对象调用方法
*4
*/
//接口
public interface SaleComputer {
String sale(double money);
void show();
}
//实现类
public class HuaWei implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("花了" + money + "元买了一台电脑");
return "华为电脑";
}
@Override
public void show() {
System.out.println("展示电脑");
}
}
//测试类
public class ProxyTest {
public static void main(String[] args) {
final HuaWei huaWei = new HuaWei();
SaleComputer huaWeiProxy = (SaleComputer) Proxy.newProxyInstance(huaWei.getClass().getClassLoader(), huaWei.getClass().getInterfaces(), new InvocationHandler() {
//增强方法体
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断是不是sale方法,是sale方法就对其进行增强
if (method.getName().equals("sale")) {
//增强参数
double money = (double) args[0];
money = money * 0.85;
System.out.println("专车接送");
Object returnValue = method.invoke(huaWei, money);
System.out.println("免费送货上门");
//增强返回值
return returnValue + " + 鼠标垫";
} else {
//原样输出,不对其进行增强
Object obj = method.invoke(huaWei, args);
return obj;
}
}
});
String computer = huaWeiProxy.sale(8000);
System.out.println(computer);
//huaWeiProxy.show();
}
}
优点:
- 对比静态代理,动态代理无需手动创建代理类
缺点:
- 被代理类必须至少实现一个接口
- 类中的所有public方法都会被处理,如果只想处理一部分方法,需要对方法名进行判断
- 对多个真实对象进行代理的话,若使用 Spring 的话配置太多了,要手动创建代理对象,用起来麻烦
总结:
JDK 动态代理:代理类与真实类共同实现一个接口
CGLIB 动态代理:代理类继承于真实类
基于子类的动态代理
涉及的类:Enhancer
创建代理对象:Enhancer.create()
创建代理对象的要求:被代理类最少需要实现一个接口,否则不能使用
create方法的参数:
-
Class:字节码
它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器 -
Callback:用于提供增强的代码,我们一般写的都是该接口的子接口实现类:MethodInterceptor
写具体需要增强的代码,通常情况下都是匿名内部类,但不是必须的
在该匿名内部类中需要实现一个intercept方法
作用:执行被代理对象的任何接口方法都会经过该方法
@param proxy
@param method
@param args
以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
@methodProxy 当前执行方法的代理对象
然后通过代理对象来调用类中的方法
public class HuaWei {
public String sale(double money) {
System.out.println("花了" + money + "元买了一台电脑");
return "华为电脑";
}
public void show() {
System.out.println("展示电脑");
}
}
//测试类
public class ProxyTest {
public static void main(String[] args) {
final HuaWei huaWei = new HuaWei();
HuaWei cglibHuaWei = (HuaWei)Enhancer.create(huaWei.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//判断是不是sale方法,是sale方法就对其进行增强
if (method.getName().equals("sale")) {
//增强参数
double money = (double) args[0];
money = money * 0.85;
System.out.println("专车接送");
Object returnValue = method.invoke(huaWei, money);
System.out.println("免费送货上门");
//增强返回值
return returnValue + " + 鼠标垫";
} else {
//原样输出,不对其进行增强
Object obj = method.invoke(huaWei, args);
return obj;
}
}
});
String computer = cglibHuaWei.sale(8000);
System.out.println(computer);
//cglibHuaWei.show();
}
}
静态代理
概述:在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。(即代理类及代理对象要我们自己创建)
- 编写一个接口,让真实类(被代理类)实现该接口
//真实类,其对象为真实对象
public class EmployeeServiceImpl implements IEmployeeService {
@Override
public void save(String name, String password) {
System.out.println("保存:" + name + "|" + password);
}
}
- 编写一个代理类,让代理类也实现该接口,在实现的方法中调用真实类中实现的方法
//代理类,其对象为代理对象
public class EmployeeServiceProxy implements IEmployeeService {
//引用真实对象
private IEmployeeService target;
public void setEmployeeService(IEmployeeService employeeService) {
this.target = employeeService;
}
//引用事务对象,事务类中提供开启、提交、回滚事务的方法
private MyTransactionManager tx;
public void setTx(MyTransactionManager tx) {
this.tx = tx;
}
@Override
public void save(String name, String password) {
try {
tx.begin();
//找真实对象来做,调用真实类中实现的方法
target.save(name,password);
tx.commit();
}catch (Exception e){
tx.rollback();
e.printStackTrace();
}
}
}
- 为代理类中注入需要的对象
<bean id="myTransactionManager" class="cn.kjcoder.tx.MyTransactionManager"/>
<!--代理对象-->
<bean id="employeeServiceProxy"class="cn.kjcoder.service.impl.EmployeeServiceProxy">
<property name="employeeService">
<!--真实对象|被代理对象-->
<bean class="cn.kjcoder.service.impl.EmployeeServiceImpl"/>
</property>
<property name="tx" ref="myTransactionManager"/>
</bean>
优点:
- 业务类中只需关注业务自身逻辑,保证了业务类的可重用性
- 把真实对象进行隐藏,保护真实对象
缺点:
- 代理对象的某个接口只服务于某一种类型的对象,当业务类多时,就需要创建多个代理类
- 若需要代理的方法很多,则要为每一种方法都进行代理处理
- 如果接口中增加一个方法,除了实现类需要实现这个方法外,代理类也需要实现此方法
(违反开闭原则)