为什么要学习代理模式
因为aop的底层机制就是动态代理
代理模式
- 静态代理
- 动态代理
静态代理:
- 代理模式的角色分析
抽象角色:一般会使用抽象类或者接口实现
真实角色:被代理的角色
代理角色:代理真是角色,代理真实角色后,一般会做一些附属的操作
客户:使用代理角色进行一些操作
用户增删改查案例
- UserService接口
public interface UserService {void add();void delete();void update();void query();
}
- UserServiceImpl(真实角色)
public class UserServiceImpl implements UserService {public void add() {System.out.println("增加一个用户");}public void delete() {System.out.println("删除一个用户");}public void update() {System.out.println("更新一个用户");}public void query() {System.out.println("查询一个用户");}
}
- UserServiceImplProxy(代理)
public class UserServiceImplProxy implements UserService{private UserServiceImpl userService;public void setUserService(UserServiceImpl userService) {this.userService = userService;}public void log(String method){System.out.println("使用了"+method+"方法");}public void add() {log("add");userService.add();}public void delete() {log("delete");userService.delete();}public void update() {log("update");userService.update();}public void query() {log("query");userService.query();}
}
- 测试
public class Test {public static void main(String[] args) {UserServiceImpl userService=new UserServiceImpl();UserServiceImplProxy userServiceImplProxy=new UserServiceImplProxy();userServiceImplProxy.setUserService(userService);userServiceImplProxy.add();userServiceImplProxy.query();}
}
租房案例
- Rent(租房方法)(接口)
//租房的接口
public interface Rent {//租房void rent();
}
- Host(真实角色)(房东)
//这个房子哟啊出租
public class Host implements Rent{//出租public void rent(){System.out.println("房东要出租房子");}
}
- Proxy(代理)(中介)
//房屋中介--代理
public class Proxy implements Rent{//房东private Host host;public Proxy() {}public void setHost(Host host) {this.host = host;}public void rent() {lookHouse();host.rent();fare();}private void lookHouse(){System.out.println("中介带你看房");}private void fare(){System.out.println("收取中介费");}
}
- You(测试)(你要租房)
public class You {public static void main(String[] args) {Host host=new Host();Proxy proxy=new Proxy();proxy.setHost(host);proxy.rent();}
}
小结
代理模式的好处
- 可以使真实角色更加纯粹,不用去关注一些公共的事情
- 公共的业务由代理去完成,实现业务的分工
- 公共业务要扩展化,可以更加集中和方便
缺点
- 如果我们的真实角色变得非常多,代理类也会随之增多,工作量变大,开发效率低!然后我们想需要一种额能够由静态代理的全部好处,但是又不存在这种缺点的东西。(引出动态代理
分割线----------------------------------------------------------------------------
动态代理
动态代理和静态代理的角色都是一样的;
区别是静态代理的代理类是我们提前写好的,动态代理的类是动态生成的;
动态代理大概分为两类;
1、基于接口实现的:jdk
2、基于类实现的:cglib
#当今用的比较多的是javasist来生成动态代理
再学习动态代理之前,我们需要哦先掌握两个类:
- InvocationHandler
- Proxy
在jdk1.8 api里搜索这两个类,找到方法
InvocationHandler
Object invoke(Object **proxy**,方法 **method**,Object[] args)throws Throwable处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。
参数
- proxy - 调用该方法的代理实例
- method -所述方法对应于调用代理实例上的接口方法的实例。 方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
- args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。 原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
Proxy
public static Object newProxyInstance(ClassLoader loader,
类<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
参数
- loader - 类加载器来定义代理类
- interfaces - 代理类实现的接口列表
- h - 调度方法调用的调用处理函数
结果
具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例
步骤解析:以租房为例
- 第一步:写一个类(InvocationHandlerProxy)去实现InvocationHandler,然后重写它的invoke方法。
- 第二步:把需要代理的对象拿过来(真实对象)(把它set进去,让代理类能使用真实对象)
private Rent rent;public void setRent(Rent rent){this.rent=rent;}
- 第三步:在重写的invoke方法里,调用method参数的invoke方法【意为可以动态的执行rent里面的方法内容】
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {lookHouse();**Object invoke = method.invoke(rent, args);**fare();return invoke;}
- 第四步:此时我们发现invoke方法的参数里还剩(Object proxy)没有定义,所以接下来开始写proxy(代理类)(动态生成代理类)
//动态生成代理类public Object getProxy(){// 得到这个类的反射再得到加载器return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);//通过类的反射得到接口 第三个参数是InvocationHandler,// 我们本身的这个类就是实现了这个Inv....ler// 所以传 this进去就可以了}
- 第五步:写测试类
-首先我们需要真实对象Host host=new Host();
-然后是动态生成代理
-然后给他真实对象(用我们前面定义的set方法)
-然后动态生成代理对象,返回Rent类型
-然后代理进行方法调用
Host host=new Host();
InvocationHandlerProxy ihp = new InvocationHandlerProxy();ihp.setRent(host);Rent proxy = (Rent) ihp.getProxy();proxy.rent();
---------------------------就此完成--------------------------
拓展,
若是我们需要代理的真实对象一开始不知道,或者是我有可能想要代理其他对象,只需要将我们上面InvocationHandlerProxy里的Rent rent 修改为Object target ,再将代码里出现的rent全部改为target就可以了