代理模式
代理模式简介
问题提出
当项目的一些方法完成后,需要增加一些功能如安全性检查,若在原来方法上增加这些功能,就需要修改源代码。
最好的办法是使用一个程序,完成增加的功能,并能调用原来方法 ,这样就不需要修改原来的代码。该程序就是代理。
代理原则
可控制原对象,但不会改变原对象的接口。
代理的种类
静态代理(能看到的,实际存在的代理类)
动态代理(是在运行期生成出来的)
被代理对象的要求
以实现接口的方式完成方法的创建。
静态代理
代理类
- 必须和目标类实现同一个接口;
- 设置关联属性,通过关联属性指向目标类对象。
代理过程
在应用程序中调用代理类中的方法,完成要求的功能。
例: 在指定方法中加入权限验证
目标类:
代理类(切面类,实现和目标类同一个接口):
主配文件:
应用程序中直接调用代理类中的方法:
加粗样式
动态代理
在程序运行过程中能够创建代理对象,完成自动装配。
思想: 把横切性的关注点单独拿出来模块化,提高可维护性。
特点: 代理类不再存在,将在运行中生成。
切面类
切面类的特点
- 实现invocationHandler接口;
- 关联属性,指向目标对象;
- 重写invoke方法。
代码示例:
package com.bjsxt.spring;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class SecurityHandler implements InvocationHandler {
/*用来指向目标对象的关联属性*/
private Object targetObject;
public Object newProxy(Object targetObject) {
/*关联属性指向目标对象的地址*/
this.targetObject = targetObject;
System.out.println("----------newProxy()---------------");
/**
* 生成代理类对象
* 第一个参数设置代码使用的类装载器,一般采用和目标类相同的类装载器
* 第二个参数设置代理类实现的接口,和目标类实现同一个接口
* 第三个参数设置回调对象,当代理对象的方法被调用时,会委派给该参数指定对象的invoke方法
*/
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);
}
/*
* 重写invoke方法
* method封装目标对象的方法名
* args封装目标对象的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
checkSecurity();
Object ret = null;
try {
//调用目标方法的真实实现
ret = method.invoke(this.targetObject, args);
}catch(Exception e) {
e.printStackTrace();
throw new java.lang.RuntimeException(e);
}
return ret;
}
/*
* 新增的安全检查方法
*/
private void checkSecurity() {
System.out.println("----------checkSecurity()---------------");
}
}
使用切面类
切面类直接在应用程序中运行:
使用方法:
- 创建切面类对象(Aspect),向Aspect中注入目标对象;
- 调用Aspect的方法:
① 将目标对象传给关联属性(targetObject);
② Aspect根据传入的3个参数——目标对象的类加载器,目标对象实现的接口,实现invoke方法的对象(切面类对象)——创建代理对象;
③ 返回创建的代理对象。
生成的代理对象具有以下特点:
- 和目标类实现同一个接口;
- 和目标对象有相同的方法声明;
- 一定拥有实现InvocationHandler接口的对象的地址。
注: 目标对象的类加载器和实现的接口都是通过反射获得的。
invoke方法:
一旦调用目标类的方法,默认先通过代理类对象执行Aspect中的invoke方法:
-
根据关联属性通过反射和传给invoke的参数method中封装的方法名拿到目标对象中对应的方法(如本例:this.targetObject.getClass().getMethod(addUser(String.class,String.class))););
-
通过传给invoke的参数args数组对象中封装的实参调用对应的方法;
-
返回调用结果。
采用配置文件的方式对AOP支持
定义切面类
xml配置文件
应用示例:
package com.bjsxt.spring;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager = (UserManager)factory.getBean("userManager");
userManager.addUser("张三", "123");
userManager.deleteUser(1);
}
}
查看运行结果:
在add开头的方法前执行了checkSecurity方法
查看对象的状态:
PS: COI容器中原本目标对象的id(userManager)对应的value指向的是代理对象,而目标对象纳入代理对象的管理。
注: 如果Pointcut中没有任何方法,则不创建代理对象,不会织入Advice,而是直接执行目标对象的方法。
采用注解方式实现对AOP的支持
引进相关的spring依赖库:
- SPRING_HOME/dist/spring.jar
- SPRING_HOME/lib/jakarta-commons/commons-logging.jar
- SPRING_HOME/lib/log4j/log4j-1.2.14.jar
- SPRING_HOME/lib/aspectj/*.jar(非常强大的AOP实现)
配置xml文件:
- 启用对Annotation的支持:在配置文件applicationContext.xml中加入<aop:aspectj-autoproxy/ >
- 将Aspect和目标对象配置在IOC容器中
注: 在spring3.0及以上的版本中是默认启动注释支持的,无需手动启动。
代码示例:
切面类:
目标类:
测试代码:
package com.bjsxt.spring;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager = (UserManager)factory.getBean("userManager");
userManager.addUser("张三", "123");
userManager.deleteUser(1);
}
}
运行结果:
使用CGLIB代理实现对AOP的支持
说明:
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换。
引入CGLIB库: SPRING_HOME/cglib/*.jar
配置:
与通过配置文件实现对AOP的支持相同
如果目标类没有实现任何接口,自动使用CGLIB代理。
JDK动态代理和CGLIB字节码生成的区别
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
注: 因为是继承,所以该类或方法最好不要声明成final
说明:本文仅用作学习笔记,无其他用途,如有冒犯可联系本人删除