学了spring的程序猿都应该知道spring中主要有三个核心,分别是:IOC(控制反转)、DI(依赖注入)、AOP(面向切面)
而在aop的底层主要就是实现技术就是jdk动态代理和CGlib动态代理这两种。我之前学习过程中也一直是经常的听到说接口的动态代理啥啥啥的,
但是到底是个啥,却是不得而知。然后刚好最近一直都在看设计模式这一块,其中也就看到了代理模式,所以就决定将这一知识点记录下来。
1、需要了解代码模式,那么首先就应了解,什么是代理模式?
百度百科:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
个人总结:举个例子,在我们电脑上,一些不用的文件直接我们直接删除,被删除的文件就都会暂时存放在回收站,这时我们如果要彻底删除就需要清空回收站,这就是传统的做法,这样的做法通常也是存在一个问题,那就是我们仅仅只能够去删除我们所能看得见的或者说我们一眼就能看到的所谓的垃圾文件,但是存在其他盘符路径比较深的文件那就很难去发掘了,这个时候我们如果想要做到更好更方便的处理这一事务,那么我们就可以在电脑上安装一个“电脑管家”这一类的软件,来代替我们做清楚电脑中的垃圾文件,同时还能够帮我们自动的清楚回收站的垃圾。从程序的角度来简单理解就是,有了代理模式以后,就能够在不改变原有的源代码的情况下, 新增一部分功能,例如框架里的日志输出等功能。
常见的代理就是三种:静态代理,jdk动态代理,cglib动态代理。
代理模式的组成:
* 抽象接口 (被目标对象和代理对象实现)
* 目标对象 (就是被我们所代理的对象)
* 代理对象 (在完成目标对象中的功能的同时,提供新的功能)
2、代码模式如何运用?
本次采用一个模拟用户登录的案例来演示代理模式。
3、静态代理模式
3.1 概述
静态代理模式要求:代理对象和目标对象必需实现同一接口,同时代理对象需要持有目标对象的实例,这样我们就可以在不修改目标对象源代码的情况下增加新的功能
3.2 创建一个接口
package com.lxd.dongtaidaili;
/**
* 用户业务逻辑层接口
*
* @ClassName:IUserService
* @author lxd
* @date 2018年10月31日
* @version
*/
public interface IUserService {
/**
* 登录功能
*
* @Title: login
* @return void
*/
public void login();
}
3.3 创建目标对象(接口的实现类)
package com.lxd.dongtaidaili;
/**
* 用户业务逻辑层实现类
*
* @ClassName:UserService
* @author lxd
* @date 2018年10月31日
* @version
*/
public class UserService implements IUserService {
/** 用户名 */
private String username;
public void setUsername(String username) {
this.username = username;
}
public UserService(String username) {
this.username = username;
}
/*
* (non-Javadoc)
* @see com.yidu.dongtaidaili.IUserService#login()
*/
@Override
public void login() {
System.out.println("欢迎用户:" + username + "登录!");
}
}
3.4 创建代理对象(需要和目标对象实现同一接口)
package com.lxd.dongtaidaili;
/**
* 代理对象
*
* @ClassName:UserServiceProxy
* @author lxd
* @date 2018年10月31日
* @version
*/
public class UserServiceProxy implements IUserService {
/** 目标对象,我们需要代理的真实对象 */
private IUserService userService;
/**
* 使用带参的构造方法给目标对象赋值
* @param userService
*/
public UserServiceProxy(IUserService userService) {
this.userService = userService;
}
@Override
public void login() {
// 简单提示功能,此处也可以换成其它的业务功能,这里只做基本演示
System.out.println("警报警报,有身份不明者正准备入侵系统!");
// 调用目标对象原有的登录功能
userService.login();
// 简单提示功能,此处也可以换成其它的业务功能,这里只做基本演示
System.out.println("警报解除,自己人");
}
}
3.5 创建测试对象
package com.lxd.dongtaidaili;
import org.junit.Test;
/**
* 测试类,测试静态代理功能
* @ClassName:UserServiceTest
* @author lxd
* @date 2018年10月31日
* @version
*/
public class UserServiceTest {
@Test
public void testLogin(){
// 创建代理对象,传入目标对象的实例
UserServiceProxy proxy = new UserServiceProxy(new UserService("张三"));
// 通过代理对象来调用登录功能
proxy.login();
}
}
3.6 测试结果
3.7 静态代理总结
通过上述的过程我们可以最直观的看出就是,最终的功能的实现是由代理对象来帮我们完成的。从功能的角度来说,这个方法很不错,但是也仅仅只是适用于需要代理的地方少,因为代理对象和目标对象都要实现同一接口,这样的话如果代理对象多了,那么如果想要做修改,那么就是一件头痛的事情,太不利于后期的代码维护了。
------------------------------------------------------------------------------------------------------------------------------ 分隔线 ------------------------------------------------------------------------------------------------------------------
4、 JDK动态代理
4.1 概述
* 动态代理和静态代理最大的区别就在于动态对象中的代理对象不需要和目标对象实现相同的接口,只需要实现JAVA API 提供的代理实例接口即可
* 说到动态代理我们首先需要了解到一个接口以及一个类,查看java jdk 得到如下解释:
InvocationHandler:是代理实例的调用处理程序 实现的接口。
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke
方法
* invoke(Object proxy, Method method, Object[] args)
在代理实例上处理方法调用并返回结果。
* proxy: 代表代理对象
* method: 代表正在执行的方法
* args: 代表调用目标方法时传入的参数
Proxy:提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类
* newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
* loader:目标对象的类加载器
* interfaces: 目标对象所实现的接口
* h: 代理对象,实现对目标对象中方法的调用
4.2 创建一个代理对象的处理程序
* 目标对象和接口就用前面静态代理中的了
package com.lxd.dongtaidaili; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 创建一个代理对象的处理程序 * * @ClassName:UserServiceHandler * @author lxd * @date 2018年10月31日 * @version */ public class UserServiceHandler implements InvocationHandler { /** 目标对象 */ private Object iuserService; /** 使用有参构造给目标对象赋值 */ public UserServiceHandler(Object iuserService) { this.iuserService = iuserService; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("动态代理:警报警报,有身份不明者正准备入侵系统!"); method.invoke(iuserService, args); System.out.println("动态代理:警报解除,自己人"); return null; } }
4.3 创建测试类
package com.lxd.dongtaidaili; import java.lang.reflect.Proxy; import org.junit.Test; /** * 测试类,测试动态代理 * @ClassName:UserTestProxy * @author lxd * @date 2018年10月31日 * @version */ public class UserTestProxy { @Test public void testProxy(){ // 创建目标对象实例 UserService target = new UserService("李四"); // 创建一个代理对象的处理程序实例 UserServiceHandler handler = new UserServiceHandler(target); // 创建目标类的代理类,后续使用代理对象执行方法底层都会被自行的执行我们自定义的handler处理程序中的invoke方法 IUserService userService = (IUserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); // 执行代理类中的方法 userService.login(); } }
4.4 执行结果如下
4.5 动态代理总结
从上述案例可以明显的看出代理类和目标类测底的分离开来,无论后续目标类中的代码如何更改,又或者更换接口等,都将于代理类没有关系,代理类只需要调用即可,不需要了解其内部的代码结构。
---------------------------------------------------- 分隔线 ---------------------------------------------------------------------------
学无止境,不可轻言放弃,哪怕每天多一点也好。