第一步:新建一个modules
第二步:创建接口,其中包含我们业务所涉及的功能
package spring_CalculatorProxy;
public interface Calculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
第三步:创建接口的实现类并重写其中的方法
package spring_CalculatorProxy;
public class CalculatorImpl implements Calculator{
@Override
public int add(int i, int j) {
System.out.println("日志,方法:add,参数"+i+","+j);//实现日志功能
//核心功能
int result=i+j;
System.out.println("方法内部:result"+result);
System.out.println("日志,方法:add,结果"+result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志,方法:sub,参数"+i+","+j);
//核心功能
int result=i-j;
System.out.println("方法内部:result"+result);
System.out.println("日志,方法:sub,结果"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志,方法:mul,参数"+i+","+j);
//核心功能
int result=i*j;
System.out.println("方法内部:result"+result);
System.out.println("日志,方法:mul,结果"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志,方法:div,参数"+i+","+j);
//核心功能
int result=i/j;
System.out.println("方法内部:result"+result);
System.out.println("日志,方法:div,结果"+result);
return result;
}
}
现有代码缺陷:
针对带日志功能的实现类,我们发现有如下缺陷:
1: 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
2: 附加功能分散在各个业务功能方法中,不利于统一维护
解决思路:
解决这两个问题,核心就是:解耦
,我们需要把附加功能从业务功能代码中抽取出来
但要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决,原因是面向对象是纵向继承机制,我们只能将连续执行的代码进行封装,而不能实现抽取任意的某几行不连续的代码,所以需要引入新的技术
其实代理模式在我们的日常生活中,也是非常常见的,比如:
广告商找明星拍广告需要通过经纪人
合作伙伴找老板谈合作约见面时间需要经过秘书
房产中介是买卖双方的代理
将非核心逻辑剥离出来以后,封装这些非核心逻辑的类,对象,方法
它是属于23种设计模式中的一种,属于结构型模式,它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用,让不属于目标方法核心逻辑的代码从目标方法中剥离出来—解耦,调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时附加功能能够集中在一起也有利于统一维护
之前我们都是调用目标方法,直接实现功能,如下所示:
使用代理模式之后,如下所示:
目标对象/类/方法:被代理“套用”了非核心逻辑代码,例如我们上述场景模拟中的Calculator
我们现在就是创建目标对象所对应的代理类,每次访问目标对象的时候,都是通过代理对象进行访问,我们就不再直接访问目标对象了,而是通过代理对象对其进行访问,代理对象是间接访问目标对象的功能,代理对象和目标对象实现的功能是相同的,那么如何说明他们实现的功能都是相同的呢?
代理对象去实现目标功能的时候,直接在代理对象中去调用目标对象实现功能的方法
拿我们上述场景模拟中的add方法来说,如果使用代理模式,那么意味着,代理对象中也会产生一个add方法,当我们需要访问add方法时,我们并不会直接访问该方法,而访问的是代理对象中的add方法,通过代理对象间接去访问
,怎么个间接法呢?
即为在代理对象中的add方法中直接去调用目标对象的add方法,代理对象能够控制目标对象方法的执行,这样我们就能保证,无论是通过目标对象实现还是通过代理对象实现,最终所实现的结果是不变的
静态代理的特点就是一对一,当前一个代理类对应一个目标类,当前代理类只能作为目标类的代理,代理对象是为目标对象创建一个访问它的对象,如果需要访问目标对象中的功能时,我们就通过代理对象来访问,既然是这样,那么是不是就可以说明只要是目标对象可实现的事情,代理对象也可以实现,从代码层面来说,目标对象所拥有的方法,代理对象也必须有,因为目标对象都是通过代理对象来进行间接访问的,我们可以通过代理对象去控制目标对象的执行过程从而加入一些额外的操作,这样可以进行代码增强的效果,代码不改变的情况下,通过代理对象间接去访问目标对象中的功能,并且在它实现功能的过程中去加入一些额外的操作
创建代理类:
1:代理类和目标类需要实现相同的功能----代理类和目标类实现相同的接口
2:目标对象是通过代理对象间接访问它----实现访问目标对象所实现的功能
package spring_CalculatorProxy;
public class Calculator_proxy implements Calculator{
//声明目标类的实现类或接口
private CalculatorImpl target;
//赋值--set方法/有参构造
public Calculator_proxy(CalculatorImpl target) {
this.target = target;
}
@Override
public int add(int i, int j) {
System.out.println("日志,方法:add,参数"+i+","+j);
//通过代理对象间接访问目标对象的功能
int result= target.add(i,j);
System.out.println("日志,方法:add,结果"+result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志,方法:sub,参数"+i+","+j);
int result= target.sub(i,j);
System.out.println("日志,方法:sub,结果"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志,方法:mul,参数"+i+","+j);
int result= target.mul(i,j);
System.out.println("日志,方法:mul,结果"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志,方法:div,参数"+i+","+j);
int result= target.div(i,j);
System.out.println("日志,方法:div,结果"+result);
return result;
}
}
编写测试类:
import org.junit.Test;
import spring_CalculatorProxy.CalculatorImpl;
import spring_CalculatorProxy.Calculator_proxy;
public class ProxyTest {
@Test
public void ProxyTest(){
//通过代理对象间接访问目标对象,因此创建代理类实例对象
Calculator_proxy calculatorProxy=new Calculator_proxy(new CalculatorImpl());
calculatorProxy.add(2,3);
}
}
输出如下所示:
日志,方法:add,参数2,3
方法内部:result,5
日志,方法:add,结果5
通过代理模式进行功能增强时,不仅只有目标功能前后,还可以通过try-catch-finally中加入额外的操作,但是并不是所有位置都可以,比如目标对象存在10行代码,我们不能说想在第五行添加就在第五行添加,虽然说我们可以在目标对象实现的过程中增加新的功能,但是我们只能在目标对象方法执行之前,执行之后,catch,finally这四个位置进行
静态代理确实实现了解耦,但是由于代码不够灵活不便于修改,就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理,但如果我们将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现,这就需要使用动态代理技术了
那么动态代理技术的动态体现在哪里呢?
答案:动态生成目标类所对应的代理类,如果我们此时需要实现动态代理,那么使用动态代理技术就使得不用创建动态代理类
,而是通过jdk提供的方法和API动态的为某一个目标类动态的创建它的代理类
,和静态代理不同的是,此时,动态代理类起初是不存在的,我们需要通过jdk提供的API在代码运行的过程中,动态创建每个目标类所对应的动态代理类
创建工厂类:
工厂类—>ProxyFactory 并不是真正的代理类,而是动态生成目标类所对应的代理类的一个工具类,该类本身并没什么功能,它并不是某一个目标类所对应的代理类,而是我们可以通过这个类帮助我们动态生成目标类的代理类,之所以为工厂,是因为它的功能是生产代理类
package spring_CalculatorProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyFactory {
//由于我们并不知道是给那个目标类进行代理,因此这里代理类的类型为Object
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//生成任意目标类型所对应的代理类
public Object getProxy(){
//1:获取加载器--由于当前类是动态生成的,所以需要通过类加载器去加载该类
//根类加载器[底层通过C实现-->主要加载核心类库],拓展类加载器[加载拓展类库],应用类加载器[加载自己写的类或者是第三方jar包],自定义类加载器
ClassLoader classLoader=this.getClass().getClassLoader();
//2:获取目标对象实现的所有接口的class对象的数组---为了保证目标类和代理类实现的功能是一致的[和静态代理类一个思想]
Class<?>[] interfaces= target.getClass().getInterfaces();
//3:执行处理:设置代理类中的抽象方法如何重写
InvocationHandler invocationHandler=new InvocationHandler() {
//代理类中的方法如何重写
//proxy:代理对象 method:要执行/重写的方法 args:要执行方法的参数列表
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志,方法名:"+method.getName()+",参数:"+ Arrays.toString(args));//额外增加的功能--->输出日志信息
//method.invoke:执行,调用这个方法
Object result= method.invoke(target,args);
System.out.println("日志,方法名:"+method.getName()+",结果:"+ result);//额外增加的功能--->输出日志信息
return result;
}
};
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);//通过反射创建代理实例,由于该过程需要类加载器/接口/执行处理,因此我们在获取相关的信息
}
}
输出如下:
日志,方法名:add,参数:[2, 8]
方法内部:result,10
日志,方法名:add,结果:10
这样的日志信息并不完整,在学习静态代理的时候,我们说过进行功能增强时,我们可以通过try-catch-finally进行额外的操作,那么下面我们就演示一下:
修改ProxyFactory类
中的getProxy方法
中的InvocationHandler中
的方法的代码,添加try-catch-finally代码块,如下所示,IDE中可使用快捷键Ctrl+Alt+t
InvocationHandler invocationHandler = new InvocationHandler() {
//代理类中的方法如何重写
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("日志,方法名:" + method.getName() + ",参数:" + Arrays.toString(args));
result = method.invoke(target, args);
System.out.println("日志,方法名:" + method.getName() + ",结果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("日志,方法名:" + method.getName() + ",异常:" +e);
} finally {
System.out.println("日志,方法名:" + method.getName() + ",方法执行完毕");
}
return result;
}
};
测试类:
import org.junit.Test;
import spring_CalculatorProxy.Calculator;
import spring_CalculatorProxy.CalculatorImpl;
import spring_CalculatorProxy.ProxyFactory;
public class ProxyTest {
@Test
public void ProxyTest(){
ProxyFactory proxyFactory=new ProxyFactory(new CalculatorImpl());
Calculator Proxy= (Calculator) proxyFactory.getProxy();//由于我们不清楚此时代理类所代理的目标类是什么,又不能使用Object类,但我们知道它实现的接口,因此可以通过上转型完成
Proxy.mul(2,4);
}
}
输出如下所示:
日志,方法名:mul,参数:[2, 4]
方法内部:result,8
日志,方法名:mul,结果:8
日志,方法名:mul,方法执行完毕
上述是未有异常出现的情况,似乎和为加try-catch-finally时的输出结果差别不大,那么下面我们进行有异常的测试:
在测试类中调用除法运算:
//被除数不能为0,因此这里会抛出ArithmeticException算数异常
Proxy.div(2,0);
测试结果如下:
动态代理有两种:
jdk代理:要求必须有接口,最终生成的代理类和目标类实现相同的接口在com.sun.proxy包下,类名为$proxy2
cglib代理:,最终生成的代理类最终会继承目标类,并且和目标类在相同的包下