1.为什么要有动态代理?
我们可以先看静态代理,静态代理的优点和代理模式的优点相同,但缺点就是和业务层通过new指明实现类来实现一个Dao层接口的弊端一样:如果想用另一个实现类来实现同一个接口,就需要修改new的实现类的类型,这就会违背尽量不修改代码的开发原则;静态代理也是如此,每个真实角色有自己的实现类,而静态代理是一个代理类代理一个真实角色,所以若想更换实现类就需要重写一个代理类,这就需要改动大量代码,工作量太大。
//要实现的接口
public interface UserDao {
public void getUser();
}
//两个实现类
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("获取用户");
}
}
public class UserDaoImpl1 implements UserDao{
@Override
public void getUser() {
System.out.println("获取用户1");
}
}
//业务层调用
public class UserServiceImpl implements UserService{
//通过UserDaoImpl实现类实现UserDao接口
private UserDao userDao=new UserDaoImpl();
}
//现在想用UserDaoImpl1实现类来实现UserDao接口,就必须修改new后面的类型为UserDaoImpl1
先看new问题的解决,我们可以不使用new指明一个具体的类型,而是利用set方法,直接将所需实现类的实例丢进这个set方法中赋给接口类对象,需要哪种就丢进去哪种,不会对原有代码进行修改:
//业务层
public class UserServiceImpl implements UserService{
private UserDao userDao;
//使用set方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
//控制层
UserService userService= new UserServiceImpl();
//想用哪种实现类就用哪种,直接丢进set方法中就好了
((UserServiceImpl) userService).setUserDao(new UserDaoImpl());
((UserServiceImpl) userService).setUserDao(new UserDaoImpl1());
上面这种方法的思想就是一开始并不用知道需要什么实现类,我只用准备一个接口实例,用户将所需要的实现类类型丢进set中,由set给这个实例赋予实现类的类型,用户丢进去哪个就可以用哪个,实现了动态赋予。那静态代理缺点的解决是不是也能这样,只需要准备好业务接口(也就是抽象角色),然后用户需要什么实现类(就是真实角色)就能用哪种实现类实现,并且还能动态生成对应真实角色的代理类呢?
出于实现类实现接口的启发,我们可以用set来实现给什么实现类就能用什么实现类;但怎样动态生成代理类呢?
我们可以想一下,如果代理类只代理一个真实角色,也就是只代理一个实现类,那写好之后肯定就改不了了,倘若要代理另一个真实角色,那就必须要改真实角色的类名;但如果代理的是一个业务接口呢?所有的同一类的实现类都是实现的同一个接口,虽然实现类的类名都不一样,但接口的接口名一样,所以根据接口来写代理类就可以代理一类业务了,所有实现类都用一个代理类。这就是动态代理和静态代理的区别所在,动态代理代理的是一类业务,静态代理代理的是一个实现类。
2.动态代理实现
上面我们说动态代理代理的是一个接口,用户选好实现类之后,使用set方法赋给这个接口实例就能动态指定实现类了,接下来就是根据接口生成代理类。
所幸动态生成代理类的工具Java已经为我们准备好了,首先提供了一个Proxy类,这个类提供了动态生成代理类以及实例的静态方法,我们只需要调用这个方法就能获取一个动态代理类的实例;同时也为我们提供了一个InvocationHandler的接口,我们只需要实现这个接口就能让生成的代理类根据不同的实现类来具体处理业务了。
2.1Proxy类
Proxy中提供了创建代理类实例的静态方法,通过执行这个方法就能生成一个代理一个接口的代理类,这是通过是利用反射实现的:
Proxy.newProxyInstance(类加载器loader,通过class文件获取的业务接口,一个InvocationHandler的实现类的实例)
2.2InvocationHandler接口
这个接口是调用处理程序接口,生成的代理类要执行具体的业务需要它来帮助调用。它所要实现的函数只有一个,那就是invoke方法调用,当生成的代理类处理业务时,会自动执行invoke函数,本质是通过反射调用对应的函数
3.演示动态代理的实现
首先准备好业务的接口以及真实角色,以增删改查的业务为例:
//业务接口,也就是抽象角色
public interface Opration {
//增
public void insert();
//删
public void delete();
//改
public void update();
//查
public void select();
}
//真实角色
public class RealRole implements Opration{
@Override
public void insert() {
System.out.println("增");
}
@Override
public void delete() {
System.out.println("删");
}
@Override
public void update() {
System.out.println("改");
}
@Override
public void select() {
System.out.println("查");
}
}
然后动态生成代理类:
//这是一个代理类的调用处理程序
public class MyInvocationHandler implements InvocationHandler {
//获取业务接口
Opration opration;
//通过set方法就能动态指定实现类
public void setOpration(Opration opration) {
this.opration = opration;
}
//生成代理一类业务的代理类
public Opration getProxy(){
//使用本类的加载器是因为都是应用类加载器,使用this可以提高在开发不同业务时代码的复用度,
//如果使用业务接口来写那么不同的业务就需要修改接口名
//然后就是通过反射的方式获取业务的接口
//最后是需要一个调用处理程序,也就是需要一个InvocationHandler的实例,而这个类就是一个实
//例,所以是this
return (Opration) Proxy.newProxyInstance(this.getClass().getClassLoader(),
opration.getClass().getInterfaces(),this);
}
//本调用处理程序只有实现invoke方法才能让代理类根据不同的实现类实现业务
//当代理类调用方法时,会自动将代理类、所调用的方法以及参数交给invoke,利用反射实现方法调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里需要传入的是已经指定实现类的业务接口实例,以及参数
//所以这就相当于去找指定实现类的方法,并将参数传入来调用这个方法,最后返回值
//不同方法的返回值类型不同,Object万类之父就能兼容各种类型,最后强制转换类型即可
Object result=method.invoke(opration,args);
return result;
}
}
客户开始测试:
public class Client {
public static void main(String[] args) {
//创建调用处理程序的对象
MyInvocationHandler mih=new MyInvocationHandler();
//获取指定真实角色,即实现类
RealRole realRole=new RealRole();
mih.setOpration(realRole);
//动态生成代理类
Opration proxy=(Opration) mih.getProxy();
//代理类处理业务,内部自动调用invoke
proxy.insert();
proxy.delete();
proxy.update();
proxy.select();
}
}
运行结果 :
4.模板
经过代码演示后可以发现,动态代理实际上是用反射实现的,并且可以提炼出一个模板,在开发一个使用动态代理的项目时只需要套模板即可:
public class MyInvocationHandler implements InvocationHandler {
//抽象角色
Object target;
//选取被代理的角色
public void setTarget(Object target) {
this.target = target;
}
//获取动态代理类(只改target)
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
//实现invoke(只改target)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=method.invoke(target,args);
return result;
}
}
5.优点
- 真实角色的功能纯粹,不受扩展功能的影响
- 扩展功能交由代理类实现,能够集中管理扩展功能
- 扩展功能都由代理类实现,分工明确
- 更为灵活,能够代理一类业务