1.前言

  最近舍友去面试遇到了关于java代理模式的问题。

  我虽然知道怎么使用,但是没有做过正经的总结,因此有了这篇随笔,好好总结一下三大代理模式底层原理。

事实上,在开发项目的时候,基本用不上代理,一般使用代理都是修改以前的代码才用到了,没人闲着在业务层多套一层代理吧???

  为什么使用代理?

  原因是不希望影响原有的业务代码的同时,添加自定义的操作,如写日志、权限拦截、发短信、发邮件等,这样的操作叫做增强操作,
但是这一般是aop【面向切面编程】做的,虽然aop底层就是使用代理做的,但是用aop方便很多,因为使用aop完全不需要改变原有任何的业务代码,
而代理还需要套一层代理类。这就是差距。
  事实上不论是代理还是aop,根本上都是竖向执行业务的,有先后循序,为了解决这个问题,后来有了消息中间件 ,可以横向执行业务,当然这都是后话。
因此 不论是 aop还是代理,更多的是希望在尽可能不改变原有的业务代码而加入其他操作,至于喜欢使用aop还是代理都是看自己的喜好了。

  代理模式分静态代理、动态代理、CGLib代理,

注意了 :

动态代理  也叫  JDK代理 、接口代理 ,
Cglib代理 也叫 子类代理

  那么这些代理模式的关系是什么?

  一开始并没有所谓的代理概念,为了解决增强操作,其实一开始就是目标类外层套一个新类做新的业务,需要执行目标类方法时,直接实例目标类执行而已,
并没有什么特别的东西,后来有心人将这种增强写法规范了格式,在代理类实现了与目标类一样的接口,重写接口内容,在里面做增强业务,不仅可以确保不写错,
还可以不用手写,这就很舒服了,于是就有了现在的静态代理写法,但是这种增强操作缺点也是显然易见的,
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
  是不是很烦?于是出来了个叫动态代理的东西,解决了静态代理的缺点,而且在代理类还不需要实现与目标类一样的接口就可以达到一样的目的,
可是,虽然代理类没有直接实现在目标类所实现的接口,但是在内存动态的创建代理对象 方法的注入参数 里 需要 目标对象实现的接口的类型 ,
因此目标类必须要最少实现一个接口 ,否则无法使用动态代理。
  那这就出了个很大的问题,有些目标类不是业务层的,仅仅是一个单独的类 ,没有实现任何接口,是不能使用动态代理的。
  这又该怎么办?
  好家伙,又出了个叫 CGLib代理的东西 ,完美解决了 动态代理的缺点,继承了动态代理的优势 ,又不需要目标类需要实现接口的要求,
但是CGLib代理 不是内置在jdk里的,需要第三方jar包导入才能使用,其实就是个底层为 ASM字节码框架 的 代码生成包,不建议直接操作ASM。
【动态代理是内置在jdk的,这就是动态代理也叫jdk代理的原因】

  总结

静态代理总结:
(1)其实,所谓的静态代理,其实就是创建一个类【名为代理类】,然后该类实现了与目标类【业务层对象】一样的接口后,
重写接口内容,里面做增强业务,也就是自定义操作,然后需要执行目标类的方法时,调用注入的目标类[即业务层对象]的方法即可,
说白了就是目标类外再套一层方法而已。
(2)重写目的是保证与目标类的方法一样,不仅可以确保不写错,还可以不用手写,这就很舒服了,
但是并不是说目标类必须要有接口才可以使用静态代理,因此,目标类不论是否有接口都可以使用静态代理。
(3)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理总结:
(1)动态代理又称为jdk代理、接口代理。
(2)动态代理解决静态代理的缺点,代理类不需要再与目标对象实现一样的接口。
(3)实例代理类后,注入目标类对象即可,然后调用代理类的方法会在内存动态的创建代理对象【使用Proxy.newProxyInstance】后将该代理对象返回,
然后强转成目标对象一样的类型即可按照目标类型方法调用,创建代理对象需要输入的参数 分别是 目标类的类加载器 、目标对象实现的接口的类型、事件处理器 ,
然后重写事件处理器里的方法,该方法内写自定义的增强业务,而目标对象的方法调用则固定使用 Object returnValue = method.invoke(target, args);完成 ,
因此,如果需要对调用的当前方法分别做不同的操作,可以使用method.getName()获取当前执行的方法名字后判断,然后执行不同的增强操作,
那么如果需要对不同的目标类做不同的操作呢?那么可以使用target.getClass().getName()获取当前注入的目标类对象的名字。
这么做的好处就是不需要创建大量动态代理类做不同的增强操作,这是与静态代理最大的区别,但是我感觉这样做会变不伦不类了。
这样做的流程是 实例代理类-》注入目标对象-》内存创建代理对象-》判断当前注入对象名称-》判断当前执行的方法-》做增强业务-》返回结果
(4)虽然代理类没有直接实现在目标类所实现的接口,但是在内存动态的创建代理对象 方法的注入参数需要 目标对象实现的接口的类型 ,
因此目标类必须要最少实现一个接口 ,否则无法使用动态代理。
(5)缺点:有些类不是业务层的,仅仅是一个单独的类 ,没有实现任何接口,是不能使用动态代理的。
CGLib代理总结:
(1)记得需要导入 cglib依赖包 ,这其实是个代码生成包,在内存中构建一个子类对象从而实现对目标对象功能的扩展。
(2)Cglib 代理 又称为 子类代理。
(3)代理类需要实现MethodInterceptor接口 ,用于重写拦截方法【intercept()】,内部的增强操作方式 与 动态代理十分相似。
(4)创建代理对象需要使用工具类Enhancer。
(5)允许代理那些那么没有实现任何接口的目标类,解决了动态代理的缺点。
(6)底层是ASM字节码框架,不建议直接操作ASM。

2.静态代理

(1)目录结构

服务层接口

package com.example.javabaisc.proxy.mstatic.service;

public interface UserService {
void getname();
}

接口实现类【即目标类】

package com.example.javabaisc.proxy.mstatic.service.serviceImpl;

import com.example.javabaisc.proxy.mstatic.service.UserService;

public class UserServiceImpl implements UserService {
@Override
public void getname() {
System.out.println("老王跑啦");
}
}

静态代理类

package com.example.javabaisc.proxy.mstatic.mproxy;

import com.example.javabaisc.proxy.mstatic.service.UserService;
import com.example.javabaisc.proxy.mstatic.service.serviceImpl.UserServiceImpl; import java.util.Date; /**
* 代理类
*/
public class UserServiceProxy implements UserService {
//final可有可不有 ,但是最后idea提示最好加上去
//类型定义必须与注入类型对应,否则需要强转
private final UserServiceImpl target; //有参构造函数,用于注入业务层的实现类
public UserServiceProxy(UserServiceImpl target){
this.target = target;
} @Override
public void getname() {
System.out.println("进入静态代理类,准备开始增强业务");
System.out.println("老王人哪去了?");
target.getname();
System.out.println("准备退出静态代理类,增强业务结束,"+new Date());
}
}

测试类

package com.example.javabaisc.proxy.mstatic;

import com.example.javabaisc.proxy.mstatic.mproxy.UserServiceProxy;
import com.example.javabaisc.proxy.mstatic.service.UserService;
import com.example.javabaisc.proxy.mstatic.service.serviceImpl.UserServiceImpl;
import org.junit.jupiter.api.Test; /**
* 静态代理
*/ public class MSTest { //无静态代理
@Test
public void t() {
//实例业务层对象【类型也可以使用接口的】
UserServiceImpl userService = new UserServiceImpl();
//执行业务层
userService.getname();
} //使用静态代理
@Test
public void t2() {
//实例业务层对象【类型也可以使用接口的】
UserServiceImpl userService = new UserServiceImpl();
//实例代理类,并注入业务层对象
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
//执行代理业务
userServiceProxy.getname();
} }

(2)执行测试类 t()方法

打印结果

(3)执行测试类 t2()方法

打印结果

3.动态代理

(1)目录结构

服务层接口

package com.example.javabaisc.proxy.mdynamic.service;

public interface UserService {
void getname();
}

接口实现类【即目标类】

package com.example.javabaisc.proxy.mdynamic.service.serviceImpl;

import com.example.javabaisc.proxy.mdynamic.service.UserService;

public class UserServiceImpl implements UserService {
@Override
public void getname() {
System.out.println("老王跑啦");
}
}

动态代理类

package com.example.javabaisc.proxy.mdynamic.mproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date; /**
* 代理类
*/ public class DynamicProxy { //目标对象
//final可有可不有 ,但是最后idea提示最好加上去
private final Object target; //有参构造函数,用于注入业务层的实现类
public DynamicProxy(Object target) {
this.target = target;
}
//方法名可随意定义,这个无关紧要,目的是调用newProxyInstance去内存动态创建代理对象
public Object getProxyInstance() {
//到内存动态创建代理对象,参数分别是:目标类的类加载器 、目标对象实现的接口的类型、事件处理器
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
//重写事件处理器的调用方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入动态代理类,准备开始增强业务");
System.out.println("老王人哪去了?");
System.out.println("目标类名字:"+target.getClass().getName());
System.out.println("方法名:"+method.getName());
//执行目标对象方法,参数分别是 目标类对象 、当前方法注入参数
Object returnValue = method.invoke(target, args);
System.out.println("准备退出动态代理类,增强业务结束," + new Date());
return returnValue;
}
});
} }

测试类

package com.example.javabaisc.proxy.mdynamic;

import com.example.javabaisc.proxy.mdynamic.mproxy.DynamicProxy;
import com.example.javabaisc.proxy.mdynamic.service.UserService;
import com.example.javabaisc.proxy.mdynamic.service.serviceImpl.UserServiceImpl;
import org.junit.jupiter.api.Test; /**
* 动态代理 ,也叫jdk代理 、接口代理
*/
public class MDTest { @Test
public void t(){ //实例目标对象【类型是接口或实现类都可以,我喜欢用接口】
UserService userService = new UserServiceImpl();
//实例动态代理对象,注入目标对象,然后在内存创建该目标对象的代理对象,返回结果强转成目标对象一样的类型
//这样好处就是只要增强操作是一样的,则可以统一使用这个动态代理类
UserService userServiceProxy = (UserService)new DynamicProxy(userService).getProxyInstance();
//执行代理对象,方法则与目标类的相同
userServiceProxy.getname(); }
}

(2)启动测试方法

打印结果

4.CGLib代理

(1)目录结构

添加依赖包,目前最新版本3.3.0

<!-- Cglib代理 依赖包-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

无实现接口的目标类

package com.example.javabaisc.proxy.mCglib.Util;

import org.springframework.stereotype.Service;

@Service
public class Mail {
public void send(){
System.out.println("发送邮件成功");
} }

CGLib代理类

package com.example.javabaisc.proxy.mCglib.mproxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method;
import java.util.Date; public class CglibProxy implements MethodInterceptor {
//目标对象
//final可有可不有 ,但是最后idea提示最好加上去
private final Object target; //有参构造函数,用于注入业务层的实现类
public CglibProxy(Object target) {
this.target = target;
} //给目标对象创建一个代理对象
public Object getProxyInstance(){
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类(代理对象)
return en.create(); } //重写拦截方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("进入Cglib代理类,准备开始增强业务");
System.out.println("准备发送邮件");
System.out.println("目标类名字:"+target.getClass().getName());
System.out.println("方法名:"+method.getName());
//执行目标对象方法,参数分别是 目标类对象 、当前方法注入参数
Object returnValue = method.invoke(target, objects);
System.out.println("准备退出Cglib代理类,增强业务结束," + new Date());
return returnValue;
}
}

启动类

package com.example.javabaisc.proxy.mCglib;

import com.example.javabaisc.proxy.mCglib.Util.Mail;
import com.example.javabaisc.proxy.mCglib.mproxy.CglibProxy; import org.junit.Test; import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; @SpringBootTest
@RunWith(SpringRunner.class)
public class CGTest { @Test
public void t() {
//实例目标类
Mail mail = new Mail();
//实例 Cglib 代理类 ,注入目标对象 ,创建代理对象,返回结果强转成目标对象一样的类型
Mail mailProxy = (Mail) new CglibProxy(mail).getProxyInstance();
//执行代理对象,方法则与目标类的相同
mailProxy.send(); } @Autowired
private Mail mail; /**
*使用javabean注解,不使用new实例化目标类
*/
@Test
public void t2() { //实例 Cglib 代理类 ,注入目标对象 ,创建代理对象,返回结果强转成目标对象一样的类型
Mail mailProxy = (Mail) new CglibProxy(mail).getProxyInstance();
//执行代理对象,方法则与目标类的相同
mailProxy.send(); }
}

(2)启动测试方法 t()

打印结果

(3)如果使用 javabean注解来实例化 目标类,

在单元测试记得启动spring注解

启动测试方法 t2()

打印结果

java 代理模式 总结的更多相关文章

  1. Java代理模式

    java代理模式及动态代理类 1.      代理模式 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目 ...

  2. Java代理模式示例程序

    Java代理模式示例程序 当然不是我想出来的,是我看的一个网上教程里的. 模拟的是一个对电脑公司的代理 真实类的接口: public interface SaleComputer { public S ...

  3. 浅谈java代理模式

    讲解java代理模式 目录 讲解java代理模式 何谓代理模式 静态代理 动态代理 JDK动态代理 CGLIB动态代理 何谓代理模式 代理模式,即Proxy Pattern,23种java常用设计模式 ...

  4. Java代理模式/静态代理/动态代理

    代理模式:即Proxy Pattern,常用的设计模式之一.代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问. 代理概念 :为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委 ...

  5. JAVA代理模式与动态代理模式

    1.代理模式 所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用.代理模式给某 ...

  6. java 代理模式一: 静态代理

    代理模式: 代理模式的作用:为其他对象提供一种代理以控制对 特定对象  的访问. 某种情况下,一个客户不想或者直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用:通过代理对象引用. ...

  7. 18 java 代理模式 (转)

    静态代理 1.新建一个接口,这个接口所提供的方法是关于数据库操作的 public interface EmployeeDao { public void updateSalary(); } 2.建一个 ...

  8. JAVA 代理模式(Proxy)

    1.代理模式 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 代理模式一般涉 ...

  9. Java代理模式——静态代理模式

    一:代理模式 代理模式的作用是:为其他对象提供一种代理以控制这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 二:代理模式设计 ...

随机推荐

  1. ssm动态查询向前台传json

    1.数据协议层 public User selectById(Integer id);//通过id值查询用户 2.数据层 <select id="selectById" re ...

  2. 【Python】数据处理分析,一些问题记录

    不用造轮子是真的好用啊 python中单引号双引号的区别 和cpp不一样,cpp单引号表示字符,双引号表示字符串,'c'就直接是ascii值了 Python中单引号和双引号都可以用来表示一个字符串 单 ...

  3. Moment.js使用笔记

    零.前情提要 上个月开发了数据平台,用的框架是vue + Ant Design of Vue,其中用了组件[range-picker]日期选择框,涉及到时间方法就去看了momentJS,以此记录~ 如 ...

  4. 如何为Dash/Zeal生成c++ 文档: 以abseil文档为例

    目录 1. 软件安装 2 Sample源文件下载: 3. 生成步骤 3.1 使用doxygen生成html文件 3.2 使用docsetutil 生成 dash/Zeal 格式 1. 软件安装: 1. ...

  5. 解决用creact-react-app新建React项目不支持 mobx装饰器模式导致报错问题 。

    创建react项目 create-react-app mobx-demo cd my-app npm run start 使用react-app-rewired npm install customi ...

  6. QT QApplication干了啥?

    ------------恢复内容开始------------ QCoreApplicationPrivate 会取得current thread; 在windows平台创建TLS变量,记录线程信息,并 ...

  7. MySQL 创建定时任务 详解

    自 MySQL5.1.6起,增加了一个非常有特色的功能–事件调度器(Event Scheduler),可以用做定时执行某些特定任务,来取代原先只能由操作系统的计划任务来执行的工作.事件调度器有时也可称 ...

  8. LuoguB2133 我家的门牌号 题解

    Update \(\texttt{2021.11.27}\) 修复了代码中的 \(10000\) 写成 \(n\) 的错误. Content 一个家庭住在一个胡同里面,门牌号从 \(1\) 开始编号. ...

  9. CF1433B Yet Another Bookshelf 题解

    Content 在一个仅有 \(0,1\) 这两个数的数列上,每次可以选择一段全为1的连续区间将其左移 \(1\) 或者右移 \(1\).现给出 \(t\) 次询问,每次询问给出一个长度为 \(n\) ...

  10. CF670A Holidays 题解

    Content 假设 \(1\) 年有 \(n\) 天,而每周同样会有 \(5\) 天工作日和 \(2\) 天休假.求一年最小的休假天数和最大休假天数. 数据范围:\(1\leqslant n\leq ...