个人博客地址:
http://xiaohe-blog.top
在分层开发中,哪个层次对我们来说更加重要呢 ?Dao?Service?Controller?
肯定是Service
层了,不管做什么样的项目,其主要任务就是通过各个Service的分工合作来满足用户的需求。Dao专注于与数据库打交道,Controller负责处理前端传来的数据。Service负责连接二者完成业务。
那么Service中包含了哪些代码 ?
我们要在Service中写两种代码 :附加功能与核心功能。
附加功能例如开启事务、记录一下这个操作进行的日期、机器的运行状态…
核心功能才是我们完成业务的代码。
附加功能的特点是什么呢?通用
,例如事务无非就是开启、提交、回滚。假如附加业务有20行,一个addUser需要这么多附加业务,那还有dalateUser、getUser、UpdateUser…甚至其他的StudentService也需要这些代码,一个项目加起来怕是有上千行这样重复的代码。
那我们能不能把它们抽取出来制作为一个类,让这个类代替Service的附加功能,需要的时候直接调用呢?
这就是代理,将附加业务抽取出来
,让其他的类完成,需要的时候直接调用即可。
这里有两个名词 :代理类、原始类。
代理类 :被抽取出来的附加功能。
原始类 :包含核心业务的类。
如果你还没有搞懂,那我们来看看代理模式典型案例 :租房 。在早期租房过程中,有两个角色 :租户、房东。
租户要去看租房信息、联系房东、看房、付钱。
房东需要去发布房子广告、带领客户看房、收钱。
按照一切皆对象的原则,我们可以将二者抽象为两个类
设想一下你是房东,你的主要目的是什么 ?收钱啊,打广告、带人看房多累的事啊我tm一年啥也不干了就在外面打广告呢,老子只想拿钱,拿钱就是我的核心业务,其他我都不想干。
这时候另一个行业兴起了 :中介。你不想打广告?我帮你打!你不想带客户看房?我帮你带!虽然我没房,但是我愿意花这个时间挣这个钱啊。于是房东将租房的事交给中介…
中介去打广告、带租户看房,等到满意了直接让房东收钱就好。
注意这里并不是我们想的房东调用中介的附加功能,而是全权交给中介,中介调用房东的核心功能。
中介就是代理类,房东是原始类。
于是我们就能总结静态代理的概念与好处 :
1. 概念: 通过代理类为原始类增加额外功能
2. 好处: 利于原始类功能的扩展与维护
并且中介的方法名也要叫“出租房屋”,不叫出租房屋叫啥?出租电动车?肯定要与房东想要的一样啊。各位可能想的是将房东设计为接口,让中介实现它。但是房东的核心功能要实现,那么房东这个接口中全部都是默认方法了,很不美观。我们让房东和中介实现同样的接口不就行了?
在这里,中介是代理对象;房东依然是原始对象。
所以我们先定义一个接口:
public interface UserService {
// 出租房屋
public void rentHome();
}
让房东和中介都实现这个接口
// 房东
public class UserServiceImpl implements UserService{
@Override
public void rentHome() {
System.out.println("收钱...");
}
}
中介完成自己的核心功能,它的核心功能就是给房东添加附加功能。
// 中介
public class UserServiceProxy implements UserService{
private UserServiceImpl fangDong = new UserServiceImpl();
@Override
public void rentHome() {
System.out.println("打广告...");
System.out.println("带客户看房...");
System.out.println("签合同...");
// 调用核心业务
fangDong.rentHome();
System.out.println("给房客提供后续业务..");
}
}
以后,我们的房东可以专注于核心业务:“收钱” 的完成,无需关心其他。
这样,我们就完成了静态代理模式的开发。
虽然刚学静态代理,但是可以看出静态代理的缺点 :
一个房东租房需要编写一个代理类 ,那如果还有什么租车、租游戏号都需要中介…那么我们是不是要写很多代理类?
马上一个项目全是代理类得了。那我们能不能让别人替我们创建代理类呢?可以使用动态代理来解决这个问题。
动态代理很重要!!!
动态代理解决了静态代理的“代理类繁杂”问题,但也付出了代价 :编写难度高。希望大家可以坚持。
动态代理分为两种:
JDK动态代理
Cglib动态代理
他们的区别会在下文介绍。
JDK为我们提供了一个接口完成动态代理的开发 :Proxy
,它有一个静态方法 newProxyInstance()
。
这个方法的参数有三个,初学者很容易被劝退,这将是我们学习的重点,也是难点。笔者将会将他们分为三个小模块分别讲解。(CGLIB代理也是这几个,现在学了,等会就不讲了)
Proxy.newInstance(
ClassLoader loader, // 类加载器
Class<?>[] interfaces, // 接口Class数组
InvocationHandler h // 某个神秘的接口
)
我们实现动态代理的主要目的是 “实现附加业务”,所以先来看看在哪里编写附加业务。
InvocationHandler
是一个接口,我们需要实现它并将实现类传进去。它只有一个方法 :invoke
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
参数 :
Object proxy
:核心业务对象。你为谁添加代附加功能?房东?那proxy就是房东对象。值得一提的是,这个proxy是代理后的对象而不是原始对象。
Method method
:核心业务。你为哪一个方法添加额外功能 ?租房?那method就是租房方法.
可以通过method.invoke(原始对象的实例, args) 调用它。
Object[] args
:核心业务的参数。
返回值:
Object
:核心业务的返回值。附加业务要与核心业务的返回值相同,今天你为房东代理,返回值为钱,明天你为宠物代理,返回的是动物…那么我们怎么知道核心业务要返回什么呢?刚才看到参数中的Method
代表核心业务,那我们是否可以执行它,获取它的返回值!本来核心业务就需要执行,我不仅执行了,还获取返回值返回,一举两得!
于是InvocationHandler
可以这样写 :
public MyInvocationHandler implements InvocationHandler {
private UserService = new UserServiceImpl();
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("打广告...");
System.out.println("带客户看房...");
System.out.println("签合同...");
// 调用核心业务,返回值保留,以后返回。
Object ret = method.invoke(userService, args);
System.out.println("给房客提供后续业务..");
return ret;
}
}
(当然用匿名内部类也可以,下面的完整编码为了减少代码量就是用了匿名内部类)
Proxy.newProxyInstance(
x,
x,
new MyInvocationHandler());
在静态代理的学习中我们说过 :代理类和原始类要实现相同的接口。并且 Class 这个参数的参数名也是 interfaces,所以这个参数就可以是 userService.getClass().getInterfaces()。
userService.getClass().getInterfaces()
Proxy.newProxyInstance(
x,
userService.getClass().getInterfaces();
new MyInvocationHandler());
学过反射应该知道这个东西,它叫类加载器
,为什么需要类加载器?不管是核心业务类的读取还是动态代理类的创建,都需要用到类加载器,相比于上面的那个类,这个就很简单了。
我们可能不知道类加载器分为几种,也不知道类加载器如何单独创建,但是我们可以借助别人的。
// 用谁的都一样,只要不是JDK内置类的类加载器就行。
// 可以是
userService.getClass().getClassLoader();
Proxy.newProxyInstance(
userService.getClass(),
userService.getClass().getInterfaces(),
new MyInvocationHandler());
学习Proxy.newProxyInstance()
的三个参数后,我们就可以完成JDK动态代理的代码编写了。
动态代理编程分为三步 :
@Test
public void test() {
//1. 创建原始对象
UserService userService = new UserServiceImpl();
//2. 匿名内部类创建 InvocationHandler
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("打广告...");
System.out.println("带客户看房...");
System.out.println("签合同...");
// 调用核心业务,返回值保留,以后返回。
Object ret = method.invoke(userService, args);
System.out.println("给房客提供后续业务..");
return ret;
}
};
//3. 调用 Proxy.newProxyInstance
UserService proxyInstance = (UserService)
Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new MyInvocationHandler()
)
proxyInstance.rentHome();
}
刚才我们在完成InvocationHandler
类时这样写 :
在完成另外两个参数时也是用了userService.getClass()
而没用使用 UserService.class
来获取类实例,为什么呢?
我们可以先试试能不能使用 :
果不其然,报错!那么为什么呢?
对于为什么不能使用proxy直接传参,刚才在介绍时已经说了,invoke需要的是原始方法,proxy是代理后对象。
那么我们将proxy换成 userService行不行呢?
可以看到依旧不行,那么问题一定在 UserService.class
和 userService.getClass()
的区别。
在学习反射我们学了获取Class实例的方法有 .class、。getClass(),也学了他俩的区别 :
.class是编译时Class实例
,它不受多态影响。
.getClass是运行时Class实例
,说白了就是多态实例。
大白话:
对于:
UserService userService = new UserServiceImpl();
UserService.class
获取的是 UserService 的Class实例。
userService.getClass()
获取的是 UserServiceImpl 的Class实例
我们可以进行验证 :
UserService userService1 = new UserServiceImpl();
UserServiceImpl userService2 = new UserServiceImpl();
System.out.println(userService1.getClass() == UserService.class);
System.out.println(userService2.getClass() == UserService.class);
System.out.println(userService1.getClass() == userService2.getClass());
// 无奖竞答:这个答案是什么?
UserService.class 获取的是 UserService 的Class
userService1 获取的是 UserServiceImpl 的Class
userService2 获取的是 UserServiceImpl 的Class
所以答案是 false、false、true。
所以在完成动态代理编码时一定要注意使用 原始对象的Class实例,而不是类的Class实例。
学习JDK动态代理后,Cglib就容易一点了。
JDK动态代理和Cglib动态代理的区别,先别说底层原理,就我们“肉眼可见”可见的最大区别,就是 :
JDK动态代理基于接口
;Cglib动态代理基于继承
。
Cglib动态代理需要代理类继承原始类
。
Cglib提供了一个类 :Enhancer
,这个类有一个create()
方法生成动态代理类。
Enhancer
不是接口,所以直接new Enhancer之后将它的成员变量赋值,例如要指定父类是谁、类加载器用啥样的…
所以在调用create()方法之前,我们需要指定几个参数 :
1、类加载器 setClassLoader();
2、父类Class实例 setSuperclass();
3、MethodInterceptor
实现类 setCallback();
前两个类已经不用讲了,现在就是一个新的类 :MethodInterceptor
接口。
(它与Springaop中的MethodInterceptor可不是同一个包下的啊,不要搞混)
接下来学习一下它。
public interface MethodInterceptor extends Callback {
Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable;
}
参数 :
(它跟InvocationHandler有三个一样的参数,就不再赘述,最后一个参数用不到,提一下。)
Object o
:代理后的对象。
Method method
:核心业务。
Object[] args
:核心业务的参数。
MethodProxy methodProxy
:生成的代理类对方法的代理引用
返回值:
Object
:这个不用说了吧,跟上面JDK动态代理InvocationHandler.invoke()的返回值一样,都代表核心业务的返回值。
于是 MethodInterceptor
可以这样写 :
public MyMethodInterceptor implements MethodInterceptor {
private UserService = new UserServiceImpl();
Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
System.out.println("打广告...");
System.out.println("带客户看房...");
System.out.println("签合同...");
// 调用核心业务,返回值保留,以后返回。
Object ret = method.invoke(userService, args);
System.out.println("给房客提供后续业务..");
return ret;
}
}
(当然用匿名内部类也可以,下面的完整编码为了减少代码量就是用了匿名内部类)
原始对象为父,代理类为子。
// 原始对象
public class UserServiceImpl {
public void rendHome() {
System.out.println("收钱...");
}
}
完整代码如下 :
@Test
public void testCglib() {
UserServiceImpl userService = new UserServiceImpl();
MethodInterceptor methodInterceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("打广告...");
System.out.println("带客户看房...");
System.out.println("签合同...");
// 调用核心业务,返回值保留,以后返回。
Object ret = method.invoke(userService, args);
System.out.println("给房客提供后续业务..");
return ret;
}
};
Enhancer enhancer = new Enhancer();
// 设置三个属性: 类加载器、父类Class实例、MethodInterceptor实例
enhancer.setClassLoader(userService.getClass().getClassLoader());
enhancer.setSuperclass(userService.getClass());
enhancer.setCallback(methodInterceptor);
// 获取代理类
UserServiceImpl userServiceProxy = (UserServiceImpl) enhancer.create();
userServiceProxy.rentHome();
}
我画了一张流程图,希望对大家有帮助 :
1、使用技术不同
JDK动态代理原理是拦截器+反射机制
,将原始类拦下进行包装。
CGLIB动态代理原理是动态字节码
,通过修改字节码生成子类。
2、受用对象不同
接口
的代理技术继承
的代理技术当有接口时使用JDK动态代理,没有接口只能继承时,使用CGLIB动态代理。
不写了,累了,种地去了