静态代理
前言(其实挺重要的)
本来静态代理不放代码的,但是没有静态代理我们很难引出动态代理,所以就从头来吧,我们一起复习一下。
我们来看下面的代码:我们做过web开发都清楚MVC模式,service层为业务层,但是在service层中,我们通常需要加入事务控制,确保程序运行的安全,如下:
这是一个最简单的service层,他只负责一个保存操作
public class IEmployeeService{
public void save();
}
public class EmployeeServiceImpl implements IEmployeeService{
public void save(){
//做保存的操作
}
}
现在我们需要加入事务的控制,在我们增加了事务控制后:
public class EmployeeServiceImpl implements IEmployeeService{
//开启资源代码
//开启事务代码
try{
public void save(){
//做保存的操作
//提交事务代码
}
}catch(Exception e){
//回滚事务代码
}finally{
//释放资源代码
}
}
我们不难看出,一个Service类居然就如此麻烦
(1)在Service的实现类中我们本来的意愿是只让它做保存操作,但是增加事务控制后,责任没有分离,我们应该只关心保存操作是什么,不应该关心日志,权限,事务等操作
(2)代码十分复杂,这才是一个类,以后有很多个Service类,后期维护很难
代理会帮助我们很好的解决问题(把Service不相干的方法剥离出来)
我们来设想一种场景,当我们需要买车,在以前,我们会直接去找车主去交易,我们交给他要买的车,以及各种信息,车主核实后交易,但其实我们发现对车主来说,核实信息其实是他的多余功能,所以诞生了一个职业,叫“瓜子二手车”,它做中介,核实信息由它来完成,顾客只看车,卖家只卖车,实现责任分离,各司其职,代理设计模式就是如此。
静态代理的主要方式是,我们的Service接口不变(引用上面两个图的代码),实现类也不变,新建一个代理类,其实大家发现了,静态代理就是把事务增强的操作,还有真实对象IEmployeeService的save方法全部放入代理类中,实现了对save方法的增强,以前的真实对象,什么都没变,这就有了保护真实对象的作用
我们就让service做自己该做的事情:
service不变就做保存操作
public class EmployeeServiceImpl implements IEmployeeService{
public void save(){
//做保存的操作
}
}
我们模拟出一个代理类:
public class EmployeeServiceProxy implements IEmployeeService{
private IEmployeeServiceImpl service;
public EmployeeServiceProxy(){
this.service = service;
}
public void save(){
//开启资源代码
//开启事务代码
try{
service.save();
//提交事务代码
}catch(Exception e){
//回滚事务代码
}finally{
//释放资源代码
}
}
}
public class Test{
public static void main(String[] args) {
IEmployeeServiceImpl service = new IEmployeeServiceImpl();
EmployeeServiceProxy proxy = new EmployeeServiceProxy(service);
proxy.save();
}
}
如果需要再加一种事务控制,那么直接在EmployeeServiceProxy类中修改就好,EmployeeServiceImpl就只做保存操作,所有你需要修改或者添加的事务操作全部移动到代理类中,这样就实现了解耦。
问题随之而来,这一个service类就一个代理类,那么很多个就需要很多个代理类,这就很麻烦,引出我们AOP的重头,动态代理。
动态代理
动态代理有两种,jdk5之后出现了jdk动态代理,还有一种cglib动态代理。
jdk动态代理
jdk动态代理就是把你的代理类交给java去管理,所谓动态代理,就是不管你要代理多少类,代理的过程不用你自己写,我们还是用上面的例子,我们既然要事务控制,那么我们就把事务操作都提取出来,我们需要做的是,这些事务操作我们怎么让这些操作有先后顺序插入我们的方法比如保存操作或者删除等操作,如下:
public class TransactionManager{
public void take() {
System.out.println("提交事务");
}
public void save() {
System.out.println("保存事务");
}
public void roll() {
System.out.println("回滚事务");
}
}
下面就是我们jdk动态代理的技术,将你自己的代理类实现一个InvocationHandler的接口,你需要覆盖一个方法invoke,我们新建一个方法getProxyObject,这个方法的作用就是获取被成功代理后的真实对象,这个方法怎么写都是死的记下就好参数的含义我在注释中已表明,主要是invoke方法,这个方法中有一个参数method,这个对象会拿到我们真实对象中的所有方法通过反射,也就是说我们假如需要对保存方法save做增强,那么这个mathod会帮助我们拿到并执行这个方法。
public class TranscationManagerAdvice implements InvocationHandler{
private Object target;//真实对象(对谁做增强)
private TransactionManager ts;
public TranscationManagerAdvice(TransactionManager ts, Object target) {
this.ts = ts;
this.target = target;
}
//创建一个代理对象
public <T> T getProxyObject() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), //类加载器,一般跟真实对象的类加载器
target.getClass().getInterfaces(),//真实对象所实现的接口(JDK动态代理必须要求真是对象有接口)
this);//如何做事务增强的对象
}
//如何为真实对象的方法做增强的具体操作,返回值是真实对象
public Object invoke(Object proxy/*proxy是代理对象*/, Method method/*当前正在被增强的方法*/, Object[] args/*被增强方法的参数*/) throws Throwable {
ts.take();
Object obj=null;
try {
obj=method.invoke(target, args);//调用对象的真实方法
ts.save();
}catch(Exception e) {
e.printStackTrace();
ts.roll();
}
return obj;
}
public class Test{
public static void main(String[] args) {
IEmployeeServiceImpl service = new IEmployeeServiceImpl();
TransactionManager ts = new TransactionManager();
IEmployeeService proxy = TranscationManagerAdvice.getProxyObject(ts, service);
proxy.save();
}
}
在这里调用主函数我们会发现实现了和静态代理打印一样的结果,但是动态代理有好处,我们不仅需要对save做事务,delete也需要,这时候怎么办呢,在接口IEmployeeService中添加delete函数,IEmployeeServiceImpl中添加delete相关的操作,然后其他什么都不用改,你会发现调用delete也会有事务,这就是我们jdk动态代理的method.invoke(target, args)这个代码会代理接口下的所有方法,是不是方便了很多。
整个下来之后,我们会发现,在这个代理类上方的import代码中,没有出现任何一个与Service相关的类,这就说明,这是动态代理,会根据不同的类进行切换。
CGLIB动态代理
它与jdk动态代理最大的区别就是,jdk动态代理是需要有Service接口,但是CGLIB动态代理不需要接口
我们的CGLIB动态代理其实和jdk动态代理没什么区别,就是写法变了
public class TranscationManagerAdvice implements InvocationHandler{
//创建一个代理对象
private Object target;
private TransactionManager ts;
public TranscationManagerAdvice(TransactionManager ts, Object target) {
this.ts = ts;
this.target = target;
}
public <T> T getProxyObject() {
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(target.getClass());//传入需要增强的真实对象
enhancer.setCallback(this);
return (T)enhancer.create();//创建代理对象
}
//如何为真实对象的方法做增强的具体操作,返回值是真实对象
public Object invoke(Object proxy/*proxy是代理对象*/, Method method/*当前正在被增强的方法*/, Object[] args/*被增强方法的参数*/) throws Throwable {
ts.take();
Object obj=null;
try {
obj=method.invoke(target, args);//调用对象的真实方法
ts.save();
}catch(Exception e) {
e.printStackTrace();
ts.roll();
}
return obj;
}
所有的代码唯一变了的是getProxyObject里的代码段,但是这里都是死的,参数或者格式,记住就好。
但是有个很重要的细节,就是我们的代理类还是实现了InvocationHandler这个接口,但是这个接口现在已经是Spring包里面的接口了,这个接口是Spring的不再是jdk的,不要复制过去搞错了,之前的是jdk包里面的,名字一模一样,一定要换成spring包底下的才可以用,我们对比一看,还是CGLIB更简单,在getProxyObject里还是先创建代理对象,然后告诉编译器需要继承于哪一个类去做增强,然后设置增强对象,最后return回去你创建并增强的代理对象。