代理模式概述
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法.
举个例子来说明代理的作用: 假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子.
代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象.通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用前处理,或调用后处理。既(AOP微实现) ,AOP核心技术面向切面编程。
代理模式应用场景
SpringAOP、事物原理、日志打印、权限控制、远程调用、安全代理 可以隐蔽真实角色
代理模式有多种应用场合,如下所述:(摘自IBM出品-对代理模式的讲解)
远程代理,也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。比如说 WebService,当我们在应用程序的项目中加入一个 Web 引用,引用一个 WebService,此时会在项目中声称一个 WebReference 的文件夹和一些文件,这个就是起代理作用的,这样可以让那个客户端程序调用代理解决远程访问的问题;
虚拟代理,是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化,比如打开一个网页,这个网页里面包含了大量的文字和图片,但我们可以很快看到文字,但是图片却是一张一张地下载后才能看到,那些未打开的图片框,就是通过虚拟代里来替换了真实的图片,此时代理存储了真实图片的路径和尺寸;
安全代理,用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候;
指针引用,是指当调用真实的对象时,代理处理另外一些事。比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它,或当第一次引用一个持久对象时,将它装入内存,或是在访问一个实际对象前,检查是否已经释放它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些内务处理;
延迟加载,用代理模式实现延迟加载的一个经典应用就在 Hibernate 框架里面。当 Hibernate 加载实体 bean 时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采取延迟加载的机制,以提高系统的性能。Hibernate 中的延迟加载主要分为属性的延迟加载和关联表的延时加载两类。实现原理是使用代理拦截原有的 getter 方法,在真正使用对象数据时才去数据库或者其他第三方组件加载实际的数据,从而提升系统性能。
代理模式的分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)
- JDK 自带动态代理(需要实现接口)
- Cglib动态代理(底层基于ASM实现:轻量级Java字节码操作框架)(不需要实现接口) 、Javassist等字节码技术
静态代理
概述
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
代码
业务类接口
public interface IUserDao {
void add();
void save();
}
业务实现类
public class UserDaoImpl implements IUserDao {
@Override
public void add() {
System.out.println("add()。。。。");
}
@Override
public void save() {
System.out.println("save()。。。。");
}
}
代理类(需要实现业务类接口)
public class UserDaoProxy implements IUserDao {
private IUserDao userDao;
public UserDaoProxy(IUserDao userDao) {
this.userDao = userDao;
}
@Override
public void add() {
System.out.println("add之前....");
userDao.add();
System.out.println("add之后....");
}
@Override
public void save() {
userDao.save();
}
}
测试
public class ClientTest {
public static void main(String[] args) {
IUserDao userDao = new UserDaoImpl();
UserDaoProxy proxy = new UserDaoProxy(userDao);
proxy.add();
proxy.save();
}
}
结果
add之前....
add()。。。。
add之后....
save()。。。。
动态代理
概述
动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。与静态处理类相比,动态类有诸多好处。首先,不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;其次,使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。
动态代理类使用字节码动态生成加载技术,在运行时生成加载类。生成动态代理类的方法很多,如,JDK 自带的动态处理
、CGLIB
、Javassist
或者 ASM
库。
JDK
的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar
包,但相对功能比较弱。
CGLIB
和 Javassist
都是高级的字节码生成库,总体性能比 JDK
自带的动态代理好,而且功能十分强大。
ASM
是低级的字节码生成工具,使用 ASM
已经近乎于在使用 Java bytecode
编程,对开发人员要求最高,当然,也是性能最好的一种动态代理生成工具。但 ASM
的使用很繁琐,而且性能也没有数量级的提升,与 CGLIB
等高级字节码生成工具相比,ASM
程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 CGLIB
或者 Javassist
。
JDK 动态代理
代码
业务类接口
public interface IUserDao {
void add();
void save();
}
业务实现类
public class UserDaoImpl implements IUserDao {
@Override
public void add() {
System.out.println("add()。。。。");
}
@Override
public void save() {
System.out.println("save()。。。。");
}
}
Hanlder处理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserDaoProxy implements InvocationHandler{
private IUserDao userDao;
public UserDaoProxy(IUserDao userDao) {
this.userDao = userDao;
}
/**
* 可以直接在Handler中写创建代理类的方法
* @return
*/
public IUserDao createProxy() {
IUserDao userDaoProxy = (IUserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
return userDaoProxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("add".equals(method.getName())) {
System.out.println("add 之前....");
Object result = method.invoke(userDao, args);
System.out.println("add 之后....");
return result;
}
return method.invoke(userDao, args);
}
}
测试方法
private static void test3() {
IUserDao userDao = new UserDaoImpl();
IUserDao userDaoProxy = new UserDaoProxy(userDao).createProxy();
userDaoProxy.add();
userDaoProxy.save();
}
测试结果
add 之前....
add()。。。。
add 之后....
save()。。。。
CGLib动态代理
需要引入第三方的jar包, 因为Spring整合了cglib ,也可以直接引入spring-core的jar包,我引入了cglib的3.2.5版本jar:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
代码
业务类
public class UserDao {
public void add() {
System.out.println("add() 。。。。");
}
public void save() {
System.out.println("save()。。。。");
}
}
createProxy方法
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGLibProxy implements MethodInterceptor{
private UserDao userDao;
public CGLibProxy(UserDao userDao) {
this.userDao = userDao;
}
public UserDao createProxy() {
//使用CGLIB生成代理
//1.生成核心类
Enhancer enhancer = new Enhancer();
//2.为其设置父类
enhancer.setSuperclass(userDao.getClass());
//3.设置回调
enhancer.setCallback(this);
//4.创建代理
return (UserDao) enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if("add".equals(method.getName())) {
System.out.println("add方法之前....");
Object result = proxy.invokeSuper(obj, args);
//System.err.println("add方法之后....");
System.out.println("add方法之后....");
return result;
}
return proxy.invokeSuper(obj, args);
// System.out.println("之前");
// Object result = proxy.invoke(userDao, args);
// System.out.println("之后");
// return result;
}
}
测试类
public class ClientTest {
public static void main(String[] args) {
UserDao userDao = new UserDao();
UserDao userDaoProxy = new CGLibProxy(userDao).createProxy();
userDaoProxy.add();
userDaoProxy.save();
}
}
结果
add方法之前....
add() 。。。。
add方法之后....
save()。。。。
疑惑(已解决)
万恶的编辑器,快捷键输入是,将out
输成了err
,我说怎么还变成红色的了!!(还是经验不足)
多次运行CGLib之后有如下结果,不得到怎么回事,有时间在记得看看proxy.invoke(..)
和 proxy.invokeSuper(...)
有什么区别