(文章目录)
本文简单的介绍了java的代理概念,针对静态和动态代理的不同定义与实现方式,并给出了详细的示例,最后给出一个综合的应用,展示动态代理的使用。
一、代理构成
1、代理介绍
代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。
- Subject角色负责定义RealSubject和Proxy角色应该实现的接口;
- RealSubject角色用来真正完成业务服务功能;
- Proxy角色负责将自身的Request请求,调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。 代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互。 代理的存在对于调用者来说是透明的,调用者看到的只是接口。 代理对象则可以封装一些内部的处理逻辑,如访问控制、远程通信、日志、缓存等。比如一个对象访问代理就可以在普通的访问机制之上添加缓存的支持。 这种模式在RMI和EJB中都得到了广泛的使用。传统的代理模式的实现,需要在源代码中添加一些附加的类。这些类一般是手写或是通过工具来自动生成。
2、应用场景介绍
- 方法性能监测,看一个方法的调用执行时间
- 日志管理,记录一个方法的前后执行情况
- 缓存,在一个方法执行前读取缓存
二、静态代理
下图是代理结构图,是典型的静态的代理模式
1、示例
将车站的售票服务抽象出一个接口TicketService,包含问询,卖票,退票功能,车站类Station实现了TicketService接口,车票代售点StationProxy则实现了代理角色的功能,类图如下所示。
1)、售票服务
public interface TicketService {
//售票
public void sellTicket();
//问询
public void inquire();
//退票
public void withdraw();
}
2)、售票
public class Station implements TicketService {
@Override
public void sellTicket() {
System.out.println("\n\t售票.....\n");
}
@Override
public void inquire() {
System.out.println("\n\t问询。。。。\n");
}
@Override
public void withdraw() {
System.out.println("\n\t退票......\n");
}
}
3)、代售点服务
public class StationProxy implements TicketService {
private Station station;
public StationProxy(Station station){
this.station = station;
}
@Override
public void sellTicket() {
// 1.做真正业务前,提示信息
this.showAlertInfo("××××您正在使用车票代售点进行购票,每张票将会收取5元手续费!××××");
// 2.调用真实业务逻辑
station.sellTicket();
// 3.后处理
this.takeHandlingFee();
this.showAlertInfo("××××欢迎您的光临,再见!××××\n");
}
@Override
public void inquire() {
// 1.做真正业务前,提示信息
this.showAlertInfo("××××欢迎光临本代售点,问询服务不会收取任何费用,本问询信息仅供参考,具体信息以车站真实数据为准!××××");
// 2.调用真实逻辑
station.inquire();
// 3。后处理
this.showAlertInfo("××××欢迎您的光临,再见!××××\n");
}
@Override
public void withdraw() {
// 1.真正业务前处理
this.showAlertInfo("××××欢迎光临本代售点,退票除了扣除票额的20%外,本代理处额外加收2元手续费!××××");
// 2.调用真正业务逻辑
station.withdraw();
// 3.后处理
this.takeHandlingFee();
}
/*
* 展示额外信息
*/
private void showAlertInfo(String info) {
System.out.println(info);
}
/*
* 收取手续费
*/
private void takeHandlingFee() {
System.out.println("收取手续费,打印发票。。。。。\n");
}
}
4)、静态代理实现
不希望静态地有StationProxy类存在,希望在代码中,动态生成器二进制代码,加载进来。 为此,使用Javassist开源框架,在代码中动态地生成StationProxy的字节码。
1、maven 依赖
<!-- https://mvnrepository.com/artifact/javassist/javassist -->
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
2、实现
import java.lang.reflect.Constructor;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;
/**
* @author alan
*
*/
public class Test {
public static void main(String[] args) throws Exception {
createProxy();
}
private static void createProxy() throws Exception
{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("org.druiddemo.StationProxy");
//设置接口
CtClass interface1 = pool.get("org.druiddemo.TicketService");
cc.setInterfaces(new CtClass[]{interface1});
//设置Field
CtField field = CtField.make("private org.druiddemo.Station station;", cc);
cc.addField(field);
CtClass stationClass = pool.get("org.druiddemo.Station");
CtClass[] arrays = new CtClass[]{stationClass};
CtConstructor ctc = CtNewConstructor.make(arrays,null,CtNewConstructor.PASS_NONE,null,null, cc);
//设置构造函数内部信息
ctc.setBody("{this.station=$1;}");
cc.addConstructor(ctc);
//创建收取手续 takeHandlingFee方法
CtMethod takeHandlingFee = CtMethod.make("private void takeHandlingFee() {}", cc);
takeHandlingFee.setBody("System.out.println(\"收取手续费,打印发票。。。。。\");");
cc.addMethod(takeHandlingFee);
//创建showAlertInfo 方法
CtMethod showInfo = CtMethod.make("private void showAlertInfo(String info) {}", cc);
showInfo.setBody("System.out.println($1);");
cc.addMethod(showInfo);
//sellTicket
CtMethod sellTicket = CtMethod.make("public void sellTicket(){}", cc);
sellTicket.setBody("{this.showAlertInfo(\"××××您正在使用车票代售点进行购票,每张票将会收取5元手续费!××××\");"
+ "station.sellTicket();"
+ "this.takeHandlingFee();"
+ "this.showAlertInfo(\"××××欢迎您的光临,再见!××××\");}");
cc.addMethod(sellTicket);
//添加inquire方法
CtMethod inquire = CtMethod.make("public void inquire() {}", cc);
inquire.setBody("{this.showAlertInfo(\"××××欢迎光临本代售点,问询服务不会收取任何费用,本问询信息仅供参考,具体信息以车站真实数据为准!××××\");"
+ "station.inquire();"
+ "this.showAlertInfo(\"××××欢迎您的光临,再见!××××\");}"
);
cc.addMethod(inquire);
//添加widthraw方法
CtMethod withdraw = CtMethod.make("public void withdraw() {}", cc);
withdraw.setBody("{this.showAlertInfo(\"××××欢迎光临本代售点,退票除了扣除票额的20%外,本代理处额外加收2元手续费!××××\");"
+ "station.withdraw();"
+ "this.takeHandlingFee();}"
);
cc.addMethod(withdraw);
//获取动态生成的class
Class c = cc.toClass();
//获取构造器
Constructor constructor= c.getConstructor(Station.class);
//通过构造器实例化
TicketService o = (TicketService)constructor.newInstance(new Station());
o.inquire();
cc.writeFile("D://test");
}
}
上述代码执行过后,会产生StationProxy的字节码,并且用生成字节码加载如内存创建对象,调用inquire()方法,会输出下面内容
××××欢迎光临本代售点,问询服务不会收取任何费用,本问询信息仅供参考,具体信息以车站真实数据为准!×××× 问询。。。。 ××××欢迎您的光临,再见!××××
通过上面动态生成的代码,发现其实现相当地麻烦在创造的过程中,含有太多的业务代码。使用上述创建Proxy代理类的方式的初衷是减少系统代码的冗杂度,但是上述做法却增加了在动态创建代理类过程中的复杂度:手动地创建了太多的业务代码,并且封装性也不够,完全不具有可拓展性和通用性。如果某个代理类的一些业务逻辑非常复杂,上述的动态创建代理的方式是非常不可取的!
这种静态的代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于Proxy和RealSubject的功能 本质上是相同的,Proxy只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。 为了解决这个问题,就有了动态地创建Proxy的想法:在运行状态中,需要代理的地方,根据Subject 和RealSubject,动态地创建一个Proxy,用完之后,就会销毁,这样就可以避免了Proxy 角色的class在系统中冗杂的问题了。
三、动态代理
1、InvocationHandler角色
Proxy角色在执行代理业务的时候,无非是在调用真正业务之前或者之后做一些“额外”业务。 代理类处理的逻辑:在调用某个方法前及方法后做一些额外的业务。 换一种思路就是在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。 那么,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是Invocation Handler。 动态代理模式的结构跟上面的静态代理模式稍微有所不同,多引入了一个InvocationHandler角色
2、InvocationHandler的作用
在静态代理中,代理Proxy中的方法,都指定了调用了特定的realSubject中的对应的方法: 在上面的静态代理模式下,Proxy所做的事情,是调用在不同的request时,调用触发realSubject对应的方法 动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。如下图所示: 在这种模式之中:代理Proxy 和RealSubject 应该实现相同的功能,这一点相当重要。 在面向对象的编程之中,如果想要约定Proxy 和RealSubject可以实现相同的功能,有两种方式:
- 定义一个功能接口,然后让Proxy 和RealSubject来实现这个接口。JDK动态代理实现方式。
- 通过继承。因为如果Proxy 继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法,来实现多态。cglib动态代理实现方式。
3、JDK的动态代理创建机制----通过接口
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class)。 比如为RealSubject这个类创建一个动态代理对象,JDK主要会做以下工作:
- 获取 RealSubject上的所有接口列表;
- 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ;
- 根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码;
- 将对应的字节码转换为对应的class 对象;
- 创建InvocationHandler 实例handler,用来处理Proxy所有方法调用;
- Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象
JDK通过 java.lang.reflect.Proxy包来支持动态代理。
1)、Proxy
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是用的最多的就是newProxyInstance 这个方法。一般情况下,使用下面的newProxyInstance方法。
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
//loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
//interfaces: 一个Interface对象的数组,表示的是将要给需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样就能调用这组接口中的方法了
//h: 一个InvocationHandler对象,表示的是当这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
2)、InvocationHandler
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。 在调用代理对象中的每一个方法时,在代码内部,都是直接调用了InvocationHandler的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法。
//在代理实例上处理方法调用并返回结果
Object invoke(Object proxy,Method method,Object[] args)
//proxy:指代所代理的那个真实对象
//method:指代的是所要调用真实对象的某个方法的Method对象
//args:指代的是调用真实对象某个方法时接受的参数
3)、JDK动态代理示例
public interface Subject {
public void rent();
public void hello(String str);
}
public class SubjectImpl implements Subject{
@Override
public void rent() {
System.out.println("I want to rent my house");
}
@Override
public void hello(String str) {
System.out.println("hello: " + str);
}
}
public class DynamicProxy implements InvocationHandler {
// 这个就是要代理的真实对象
private Object subject;
// 构造方法,给要代理的真实对象赋初值
public DynamicProxy(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
// 在代理真实对象前可以添加一些自己的操作
System.out.println("before rent house");
System.out.println("Method:" + method);
// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object result = method.invoke(subject, args);
// 在代理真实对象后也可以添加一些自己的操作
System.out.println("after rent house");
return result;
}
}
public class Client {
public static void main(String[] args) {
// 要代理的真实对象
Subject realSubject = new SubjectImpl();
// 要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynamicProxy(realSubject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),这里为代理对象提供的接口是真实对象所实行的接口,
* 表示要代理的是该真实对象,这样就能调用这组接口中的方法了
* 第三个参数handler, 这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
System.out.println(subject.getClass().getName());
subject.rent();
subject.hello("world");
}
4、cglib动态代理类的机制--通过类继承
“CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。” cglib 创建某个类A的动态代理类的模式
- 查找A上的所有非final的public类型的方法定义;
- 将这些方法的定义转换成字节码;
- 将组成的字节码转换成相应的代理的class对象;
- 实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)
1)、代理类
public class CGlibClass {
public void testproxy()
{
System.out.println("whatever.....");
}
}
2)、代理实现类
public class Hacker implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("方法调用之前执行的内容");
proxy.invokeSuper(obj, args);
System.out.println("方法调用之后执行的内容");
return null;
}
}
3)、使用
public class Client {
public static void main(String[] args) {
Hacker hacker = new Hacker();
//cglib 中加强器,用来创建动态代理
Enhancer enhancer = new Enhancer();
//设置要创建动态代理的类
enhancer.setSuperclass(CGlibClass.class);
// 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截
enhancer.setCallback(hacker);
CGlibClass proxy = (CGlibClass) enhancer.create();
proxy.testproxy();
}
}
4)、maven依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
四、综合应用示例
一般普通的用户管理,在原有的业务基础上增加缓存操作以及针对数据操纵方法增加事务管理功能,其他不变。针对该需求,可以通过使用动态代理实现。 本例使用jdk和cglib两个版本来实现,比较其差异。
1、业务功能实现
1)、User bean
@Data
public class User{
private Integer id;
}
2)、UserDao
public interface UserDao {
void save(User user);
User getUserById(Integer id);
}
3)、UserDaoImpl
public class UserDaoImpl implements UserDao {
@Override
public void save(User user) {
System.out.println("user is saved.");
}
@Override
public User getUserById(Integer id) {
System.out.println("getUserById is return.");
return new User();
}
}
4)、Userservice
public interface Userservice {
void save(User user);
User getUserById(Integer id);
}
5)、UserServiceImpl
public class UserServiceImpl implements Userservice {
UserDao userDao = new UserDaoImpl();
@Override
public void save(User user) {
userDao.save(user);
}
@Override
public User getUserById(Integer id) {
return userDao.getUserById(id);
}
}
6)、UserController
public class UserController {
Userservice userservice = new UserServiceImpl();
@Test
public void save() {
User user = new User();
userservice.save(user);
}
@Test
public void getUserById() {
userservice.getUserById(1);
}
}
以上为原来的业务需求的满足情况
2、JDK版本实现代理
下面是针对新的业务需求而修改的内容,即针对UserDao实现了一个代理类,同样需要修改的UserServiceImpl以及UserController。
1)、UserDaoCacheProxy
public class UserDaoCacheProxy implements InvocationHandler {
private Object userDao;
public UserDaoCacheProxy(Object userDao) {
this.userDao = userDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
String methodName = method.getName();
if (methodName.startsWith("get")) {
System.out.println("查询缓存");
if (methodName.equals("getUserById")) {
result = getUserById((Integer) args[0]);
if(result == null){
result = method.invoke(userDao, args);
}
} else {
result = method.invoke(userDao, args);
}
} else if (methodName.startsWith("save") || methodName.startsWith("update") || methodName.startsWith("delete") || methodName.startsWith("insert")) {
//System.out.println("进行事务管理");
System.out.println("开启事务");
result = method.invoke(userDao, args);
System.out.println("关闭事务");
}
System.out.println("方法返回结果:"+result);
return result;
}
public User getUserById(Integer id) {
System.out.println("proxy getUserById is return.");
return null;
}
public List<User> getUsers() {
System.out.println("proxy getUsers is return.");
return new ArrayList();
}
}
2)、UserServiceProxyImpl
public class UserServiceProxyImpl implements Userservice {
UserDao userDaoImpl = new UserDaoImpl();
InvocationHandler handler = new UserDaoCacheProxy(userDaoImpl);
UserDao userDao = (UserDao) Proxy.newProxyInstance(handler.getClass().getClassLoader(), userDaoImpl.getClass().getInterfaces(), handler);
@Override
public void save(User user) {
userDao.save(user);
}
@Override
public User getUserById(Integer id) {
return userDao.getUserById(id);
}
}
3)、UserController
public class UserController {
Userservice userservice = new UserServiceProxyImpl();
@Test
public void save() {
User user = new User();
userservice.save(user);
}
@Test
public void getUserById() {
userservice.getUserById(1);
}
}
以上即可完成我们的需求。
1)、UserServiceProxyImpl2
下面另外一种实现是直接将代理类写在UserServiceImpl中,这样就不需要再定义UserDaoCacheProxy.java文件了,修改后的UserServiceImpl文件如下
public class UserServiceProxyImpl2 implements Userservice {
UserDao userDao = getUserDaoProxy();
public UserDao getUserDaoProxy() {
UserDao userDao = (UserDao) Proxy.newProxyInstance(
UserDaoImpl.class.getClassLoader(),
// new Class[]{UserDao.class},
UserDaoImpl.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
String methodName = method.getName();
if (methodName.startsWith("get")) {
System.out.println("查询缓存");
if (methodName.equals("getUserById")) {
result = getUserById((Integer) args[0]);
if (result == null) {
result = method.invoke(new UserDaoImpl(), args);
}
} else {
result = method.invoke(new UserDaoImpl(), args);
}
} else if (methodName.startsWith("save") || methodName.startsWith("update") || methodName.startsWith("delete") || methodName.startsWith("insert")) {
System.out.println("开启事务");
result = method.invoke(new UserDaoImpl(), args);
System.out.println("关闭事务");
}
System.out.println("方法返回结果:" + result);
return result;
}
public User getUserById(Integer id) {
System.out.println("proxy getUserById is return.");
return null;
}
});
return userDao;
}
@Override
public void save(User user) {
userDao.save(user);
}
@Override
public User getUserById(Integer id) {
return userDao.getUserById(id);
}
}
2)、UserController
public class UserController {
Userservice userservice = new UserServiceProxyImpl2();
@Test
public void save() {
User user = new User();
userservice.save(user);
}
@Test
public void getUserById() {
userservice.getUserById(1);
}
}
3、cglib版本实现
javassist的执行逻辑和cglib的很像,是在代理类实例上调用代理方法。
import javassist.util.proxy.MethodHandler;
import java.lang.reflect.Method;
public class TestMethodHandler implements MethodHandler {
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
System.out.println(self.getClass());
System.out.println(thisMethod.getName());
System.out.println(proceed.getName());
Object result = proceed.invoke(self, args);
return result;
}
}
1)、UserDaoCacheProxy
public class UserDaoCacheProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {
Object result = null;
String methodName = method.getName();
if (methodName.startsWith("get")) {
System.out.println("查询缓存");
if (methodName.equals("getUserById")) {
result = getUserById((Integer) args[0]);
if (result == null) {
result = proxy.invokeSuper(obj, args);
}
} else {
result = proxy.invokeSuper(obj, args);
}
} else if (methodName.startsWith("save") || methodName.startsWith("update") || methodName.startsWith("delete") || methodName.startsWith("insert")) {
//System.out.println("进行事务管理");
System.out.println("开启事务");
result = proxy.invokeSuper(obj, args);
System.out.println("关闭事务");
}
System.out.println("方法返回结果:" + result);
return result;
}
public User getUserById(Integer id) {
System.out.println("proxy getUserById is return.");
return null;
}
}
3)、UserServiceImpl
public class UserServiceImpl implements Userservice {
UserDao userDao = getProxy();
private UserDao getProxy() {
UserDaoCacheProxy hacker = new UserDaoCacheProxy();
//cglib 中加强器,用来创建动态代理
Enhancer enhancer = new Enhancer();
//设置要创建动态代理的类
enhancer.setSuperclass(UserDaoImpl.class);
// 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截
enhancer.setCallback(hacker);
UserDao userDao = (UserDaoImpl) enhancer.create();
return userDao;
}
@Override
public void save(User user) {
userDao.save(user);
}
@Override
public User getUserById(Integer id) {
return userDao.getUserById(id);
}
}
3)、UserController
public class UserController {
Userservice userservice = new UserServiceImpl();
public void save() {
User user = new User();
userservice.save(user);
}
public void getUserById() {
userservice.getUserById(1);
}
}
标签:java,示例,void,代理,System,println,public,out
From: https://blog.51cto.com/alanchan2win/6471246