引入
什么是代理
- 张三需要100万,但是到银行取钱太麻烦了,需要办理的手续很多,怎么办?找个人帮张三去取钱,这个人去处理这些麻烦事,这个人就是代理 2
- 日常生活中,找人给你带份饭、找人替你跑腿……这些就是代理;代替你去做某些事,处理一些事情
为什么使用代理
- 程序中使用代理,在不修改目标类的情况下,能够对其功能进行增强(取钱的案例,在不影响我正常生活的情况下,有人帮我去取钱,办理手续)
- 程序中使用代理,可以隐藏目标类的具体实现(做人要低调)
代理分类
1 静态代理
JAVA静态代理是指由程序员创建或工具生成的代理类,这个类在编译期就已经是确定了的,存在的。
1、定义统一接口
public interface Person {
/**
* 接口中定义取钱方法,目标类和代理类实现该接口
* 即目标类和代理类具有相同的功能
*/
void drawMoney();
}
2、定义目标类
public class TargetPerson implements Person{
@Override
public void drawMoney() {
System.out.println("张三取到了100W");
}
}
3、定义代理类
//1 代理类实现和目标类同样的接口,具备同样的功能,这样才能做代理
public class ProxyPerson implements Person{
// 2 定义私有化目标对象,注意,不提供get、set方法
private Person target;
// 3对外提供唯一构造函数初始化目标对象
public ProxyPerson(Person target) {
this.target = target;
}
/**
* 4看似是代理人取钱,实际上代理人没那么多前,还是取的目标对象的钱
* 但是代理人可以在取钱之前,之后做一些其他事情
* 方法增强了,但是也没有影响到目标类代码
*/
@Override
public void drawMoney() {
System.out.println("代理人办理取款手续……");
target.drawMoney();
System.out.println("代理人帮忙把钱打包回来or换成欧元~……");
}
}
4、测试
public static void main(String[] args) {
//创建目标对象,你要取钱,你得出面
Person target = new TargetPerson();
//根据目标对象的要求,找到代理对象
//由于只提供了这么一个构造函数,那么就要求在使用的时候必须根据目标对象创建代理
Person proxy = new ProxyPerson(target);
//代理对象帮忙去取钱
proxy.drawMoney();
}
测试结果:
静态代理实现总结
静态代理模式一般包含三类角色:
1.抽象角色:它的作用是定义一组行为规范。抽象角色一般呈现为接口(或抽象类),这些接口(或抽象类)中定义的方法就是待实现的。(张三找人带份饭,他得找个具备带饭功能的代理,不能找个水杯去带饭)
2.真实角色:实现了抽象角色所定义的行为。真实角色就是个普通的类,它需要实现抽象角色定义的那些接口。
3.代理角色:代表真实角色的角色。根据上面代理的定义,我们可以知道代理角色需要至少完成(或实现)真实角色的功能。为了完成这一使命,那么代理角色也需要实现抽象角色所定义的行为(即代理类需要实现抽象角色所定义的接口),并且在实现接口方法的时候需要调用真实角色的相应方法。
静态代理模式实现步骤:
- 创建接口|抽象类,定义行为规范
- 创建目标类,实现接口|抽象类,重写行为
- 创建代理类:
3.1 实现和目标同同样的接口|抽象类
3.2 定义真实角色对象并且私有化
3.3 定义带参构造函数,为自定义的真实角色对象进行初始化
3.4 重写行为,调用真实角色对象的对应方法
静态代理存在的问题
静态代理存在以下问题:
- 代理类依赖于真实类,因为代理类最根本的业务功能是需要通过调用真实类来实现的。那么如果事先不知道真实类,该如何使用代理模式呢?
- 一个真实类必须对应一个代理类,即当有多个真实类RealA、RealB、RealC…的时候,就需要多个代理类ProxyA、ProxyB、ProxyC…。这样的话如果大量使用静态代理,容易导致类的急剧膨胀。该如何解决?
要想解决上述问题,就需要使用下面讲解的JAVA动态代理。
2 动态代理
什么是JAVA动态代理
JAVA动态代理与静态代理相对,静态代理是在编译期就已经确定代理类和真实类的关系,并且生成代理类的。而动态代理是在运行期利用JVM的反射机制生成代理类,这里是直接生成类的字节码,然后通过类加载器载入JAVA虚拟机执行。现在主流的JAVA动态代理技术的实现有两种:一种是JDK自带的,就是我们所说的JDK动态代理,另一种是开源社区的一个开源项目CGLIB
什么是jdk动态代理
JDK动态代理的实现是在运行时,根据一组接口定义,使用Proxy、InvocationHandler等工具类去生成一个代理类和代理类实例。
JDK动态代理的类关系模型和静态代理看起来差不多。也是需要一个或一组接口来定义行为规范。需要一个代理类来实现接口。区别是没有真实类,因为动态代理就是要解决在不知道真实类的情况下依然能够使用代理模式的问题。
jdk动态代理案例
分析:jdk动态代理创建代理对象的时候,和静态代理一样,要知道目标对象是谁(ClassLoader),和目标对象实现同样的接口(interfaces),由代理类执行方法的时候要怎么处理(InvocationHandler)
实现方式1
public static void main(String[] args) {
//定义目标对象
Person target = new TargetPerson();
//jdk创建动态代理
Person proxy = (Person)Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("啦啦啦,代理对象去办手续啦-------前置处理");
Object result = method.invoke(target, args);
System.out.println("啦啦啦,事情办完了,代理对象来进行收尾工作------后置处理");
return result;
}
});
//代理对象开始干活
proxy.drawMoney();
}
运行结果:
实现方式2
自定义invocationHandler,
public class MyInvocationHandler implements InvocationHandler{
private Object target;//定义目标对象,因为不知道目标对象会是什么类型,所以obj
public MyInvocationHandler(Object target) {//同静态代理
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("啦啦啦,代理对象去办手续啦-------前置处理");
Object result = method.invoke(target, args);
System.out.println("啦啦啦,事情办完了,代理对象来进行收尾工作------后置处理");
return result;
}
}
测试代码:
public static void main(String[] args) {
//定义目标对象
Person target = new TargetPerson();
//jdk创建动态代理
Person proxy = (Person)Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)/*篇幅没那么大了,也方便修改和以后的使用*/
);
//代理对象开始干活
proxy.drawMoney();
}
测试结果同样ok
curd小项目上使用动态代理实现日志记录和性能统计
目录结构:
关键代码:
** 1 自定义动态代理工厂**
package com.bjsxt.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 自定义代理工厂
* @author Administrator
* */
public class MyProxyFactory {
//根据目标对象,动态生成代理对象
public Object proxyCreator(Object target) {
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Date start = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("hh:MM:ss");
System.out.println("日志记录"+sdf.format(start)+"开始执行"+method.getName()+"方法");
Object result = method.invoke(target, args);
Date end = new Date();
System.out.println("性能测试:执行"+method.getName()+"方法消耗时间:"+(end.getTime()-start.getTime())+"毫秒");
return result;
}
});
return proxy;
}
}
** 2 修改servlet代码,为service实现类创建代理 **
public class ProServlet extends BaseServlet {
//目标对象
ProService target = new ProServiceImpl();
//自定义代理工厂
MyProxyFactory factory = new MyProxyFactory();
//自定义代理工厂根据目标对象,动态创建代理对象
ProService service = (ProService)factory.proxyCreator(target);
public void findAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//代理对象执行增删改查方法
List<Product> list = service.findAll();
request.setAttribute("list", list);
request.getRequestDispatcher("/list.jsp").forward(request, response);
}
public void addPro(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pname = request.getParameter("pname");
int price = 0;
String priceStr = request.getParameter("price");
if (priceStr != null && !priceStr.equals("")) {
price = Integer.parseInt(priceStr);
}
String ptype = request.getParameter("ptype");
String pdate = request.getParameter("pdate");
//代理对象执行增删改查方法
int flag = service.add(pname, price, ptype, pdate);
if (flag > 0) {
response.sendRedirect(request.getContextPath() + "/pro?method=findAll");
} else {
response.sendRedirect(request.getContextPath() + "/add.jsp");
}
}
//……其他方法不贴了,没什么关键的
** 运行效果 **
什么是cglib动态代理
JDK动态代理必须提供一些接口才能代理,在一些不能提供的接口环境下,只能采取其他第三方技术,比如CGLIB动态代理,他的优势在于不需要提供接口,只要一个非抽象类就能实现动态代理。
1.1 CGLIB代理相关的类
- net.sf.cglib.proxy.Enhancer:主要的增强类。它可以为目标类动态生成一个子类使方法可以被拦截
- net.sf.cglib.proxy.MethodInterceptor:主要的方法拦截类,它是Callback接口的子接口,需要用户实现;它在AOP方面实现了环绕通知,也就是说你既可以在调用父类方法之前和之后调用自定义的增强代码。此外,你可以在调用父类方法前修改入参,甚至是根本就不调用父类方法。
- net.sf.cglib.proxy.MethodProxy:JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。
- 其余的还有很多,暂时不做学习
1.2 CGLIB动态代理原理图
CGLIB动态代理的原理就是用Enhancer生成一个目标类的子类,并且设置好callback到proxy, 则目标类的每个方法调用都会转为调用实现了MethodInterceptor接口的proxy的intercept() 函数,如图在intercept()函数里,除执行目标类的原方法,还可以在原方法前后加入其他需要实现的过程,改变原方法的参数值,即可以实现对目标类的代理了。这似于AOP中的around advice。
撸代码:
目标类
//目标类,无接口
public class SomeServiceImpl {
public SomeServiceImpl() {
System.out.println("SomeServiceImpl无参构造器执行!");
}
public String doSome() {
return "Hong Kong police, you are laborious";
}
}
cglib动态代理工厂
//cglib动态代理工厂
public class CglibProxyFactory implements MethodInterceptor{
//创建cglib代理对象的方法,用Enhancer生成一个目标类的子类,设置好
public Object proxyCreator(Class clazz){
//创建增强器
Enhancer enhancer = new Enhancer();
//设置父类(指定目标类)
enhancer.setSuperclass(clazz);
//指定执行父类方法的时候回调当前类的intercept方法
enhancer.setCallback(this);
//为目标类创建子类(cglib动态代理对象)
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("----啦啦啦,我是前置处理");
//执行源方法(目标对象的方法)
String result = methodProxy.invokeSuper(proxy, args).toString();
System.out.println("----此处对目标类方法的返回结果做处理");
return result.toUpperCase();
}
}
运行结果:
参考链接:https://www.jianshu.com/p/1a76e516aa53