代理处于访问者与被访问者之间,可以隔离两者之间的直接交互,代理拥有被代理这的职能,所以代理可以减小被访问者的负担.另外代理可以在访问请求之前或者之后加入特定的逻辑,比如安全访问限制
代理分为两种一种是静态代理,一种是动态代理
代理模式有四种角色
- ISubject 该接口是被访资源的方法抽象
- SubjectImpl 被访问资源的具体实现类
- SubjectProxy 代理,该类实现ISubject接口,同时持有ISubject接口的一个实例
- Client 代表访问者的抽象角色
静态代理
我们通过一个例子来讲解静态代理
对于某一个资源接口我们规定只有在特定的时间才可以调用,接口如下
public interface IRequestable {
void request();
}
下面这个是接口的具体实现类
public class RequestableImpl implements IRequestable {
@Override
public void request() {
System.out.println("request proceed in RequestableImpl");
}
}
这个实现类本身没有时间验证逻辑,我们需要将时间验证逻辑通过代理实现
public class ServiceControlRequestableProxy implements IRequestable {
//真正的被访问者
private IRequestable requestable;
public ServiceControlRequestableProxy(IRequestable requestable) {
this.requestable = requestable;
}
//加入时间验证逻辑用于增强被访问者request方法
@Override
public void request() {
if (!checkTime()) {
return;
}
requestable.request();
}
public boolean checkTime() {
//验证时间
return true;
}
}
调用方法如下
private static void staticProxy() {
IRequestable target = new RequestableImpl();
IRequestable proxy = new ServiceControlRequestableProxy(target);
proxy.request();
}
上述的代码就实现了一个简单的代理模式
静态代理存在一个缺陷,如果IRequestable接口有多个实现类,我们需要一个个实例化IRequestable对象,并设置代理,这就造成了代码的冗余,于是动态代理就应运而生了
JDK动态代理
jdk1.3之后引入了动态代理机制,我们可以为指定的接口在系统运行期间动态的生成代理对象,从而帮助我们走出最初使用静态代理的困境
我们使用动态代理改写上面的例子,首先我们实现InvocationHandler这个接口,这个接口的方法就是完成横切逻辑的载体
public class RequestControlProxy implements InvocationHandler {
private Object target;
public Object getInstance(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!checkTime()) {
return null;
}
return method.invoke(target, args);
}
//时间检验逻辑
public boolean checkTime() {
return false;
}
}
在这个类中还有一个getInstance方法,这个方法用于获取一个代理
调用方法如下所示
private static void dynamicProxy(){
RequestControlProxy proxy=new RequestControlProxy();
IRequestable requestable= (IRequestable) proxy.getInstance(new RequestableImpl());
requestable.request();
}
当proxy动态生成的代理对象上相应的接口被调用的时候,对应的InvocationHandler就会拦截相应的方法调用,并进行处理
我们回过头看下JKD动态代理有解决静态代理所遇到的问题么
静态代理存在的问题在于当符合条件的Jointpoint不止一个的时候我们需要为每个Jointpoint创建代理,那么动态代理又是如何解决这个问题的
现在有RequestableImpl2类实现了IRequestable接口,我们需要代理这个类
private static void dynamicProxy(){
RequestControlProxy proxy=new RequestControlProxy();
//第一个Jointpoint
IRequestable requestable= (IRequestable) proxy.getInstance(new RequestableImpl());
requestable.request();
//第二个jointp
IRequestable requestable1= (IRequestable) proxy.getInstance(new RequestableImpl2());
requestable1.request();
}
可以看到我们无需再生产代理对象了
CGLIB动态代理
JDK动态代理局限性在于代理的对象必须实现相应的接口,CGLIB动态代理可以弥补JDK动态代理的不足
CGLIB动态代理的原理是使用动态字节码生成技术扩展对象行为,我们可以对目标对象进行继承为其生成相应的子类,而子类可以通过覆盖扩展父类的行为将横切逻辑的实现放入子类中,然后让系统使用扩展后的目标子类就能达到与代理模式相同的效果了
我们再来看看如何使用CGLIB实现动态代理
首先是父类方法也就是需要被代理的方法
public class CglibRequestable {
public void request(){
System.out.println("Cglib Requestable");
}
}
下面是实现横切逻辑
public class RequestCtrlCallback implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if (!timeCheck()){
System.out.println("service is not aviliable");
return null;
}
return methodProxy.invokeSuper(o,args);
}
public boolean timeCheck() {
return false;
}
}
测试
private static void cglib(){
//enhancer生成我们需要的代理实例
Enhancer enhancer=new Enhancer();
//设置父类
enhancer.setSuperclass(CglibRequestable.class);
//设置横切逻辑
enhancer.setCallback(new RequestCtrlCallback());
//生成代理
CglibRequestable proxy= (CglibRequestable) enhancer.create();
proxy.request();
}
代理模式应用之注解与动态代理解耦权限验证
在开发过程中,常常会遇到下面这样的权限验证场景,比如某个方法只能某些类别的用户才能调用。以前我是这样开发的
public class ReadDataService implements EmployeeGateway {
//查看某数据
public void readData() {
String currentUser=AccessControl.getCurrentUser();
if (currentUser!="leader")
throw new RuntimeException("权限不足");
System.out.println("reading data ...");
}
}
这样做的坏处是将权限验证与查看数据业务耦合在一起,并且如果我们不仅有readData()还有openFile(),deleteFile()…我们还需要继续一个一个在这些方法真正的业务逻辑前进行权限验证,这样就造成了代码的重复。今天看到一个案例使用了注解和动态代理实现权限的验证,这种方法可以避免上述的问题
JDK动态代理实现
首先定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredRoles {
String[] value();
}
这个注解用于注释方法,value值代表了可以访问这个方法的用户
因为JDK动态代理是基于接口的所以我们要定义接口
public interface EmployeeGateway {
@RequiredRoles("leader")
void readData();
}
@RequiredRoles(“leader”)注释表明该方法只能是leader角色进行访问,@RequiredRoles(“leader”)中的leader可以设置成枚举类型来避免直接编码造成的错误
定义代理类
public class AccessProxy implements InvocationHandler {
private Object accessObj;
public Object getInstance(Object target) {
this.accessObj=target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getDeclaringClass()+" -> "+method.getName());
RequiredRoles anno = method.getAnnotation(RequiredRoles.class);
//如果有权限控制,进行权限控制
if (anno != null) {
//获取注释可以访问该方法的用户
String[] roles = anno.value();
//通过某种方法获得当前调用方法的用户
String currentUser = AccessControl.getCurrentUser();
//如果用户权限不足,抛出异常
if (!Arrays.asList(roles).contains(currentUser)) {
throw new RuntimeException("current user -> " + currentUser + " is not allowed for invoking this method");
}
}
return method.invoke(accessObj, args);
}
}
在main函数中测试下
public static void main(String[] args) {
AccessProxy accessProxy=new AccessProxy();
EmployeeGateway service= (EmployeeGateway) accessProxy.getInstance(new ReadDataService());
service.readData();
}
运行结果
Exception in thread "main" java.lang.RuntimeException: current user -> visitor is not allowed for invoking this method
at com.java.annotation.example3.AccessProxy.invoke(AccessProxy.java:23)
at com.sun.proxy.$Proxy0.readData(Unknown Source)
at com.java.annotation.example3.Main.main(Main.java:7)
visitor用户调用该方法,权限不足,抛出异常
CGLIB动态代理实现
JDK的动态代理机制只能代理实现了接口的类,而没有实现接口的类就不能实现JDK的动态代理
cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
RequiredRoles anno = method.getAnnotation(RequiredRoles.class);
//如果有权限控制,进行权限控制
if (anno != null) {
//通过注释获取可以访问该方法的用户
String[] roles = anno.value();
//通过某种方法获得当前调用方法的用户
String currentUser = AccessControl.getCurrentUser();
int i = 0;
//如果用户权限不足,抛出异常
if (!Arrays.asList(roles).contains(currentUser)) {
throw new RuntimeException("current user -> " + currentUser + " is not allowed for invoking this method");
}
}
return methodProxy.invokeSuper(o, objects);
}
}
main函数如下
public static void main(String[] args) {
CglibProxy cglibProxy=new CglibProxy();
OpenFileService service= (OpenFileService) cglibProxy.getInstance(new OpenFileService());
service.openFile();
}
结果
Exception in thread "main" java.lang.RuntimeException: current user -> visitor is not allowed for invoking this method
at com.java.annotation.example3.CglibProxy.intercept(CglibProxy.java:35)
at com.java.annotation.example3.OpenFileService$$EnhancerByCGLIB$$a84881c7.openFile(<generated>)
at com.java.annotation.example3.CglibMain.main(CglibMain.java:7)