AOP切面编程
什么是AOP
AOP是面向切面编程。全称:Aspect Oriented Programming
面向切面编程指的是:程序是运行期间,动态地将某段代码插入到原来方法代码的某些位置中。这就叫面向切面编程。
一个简单计算数功能加日记
准备计算器相关类
计算接口
public interface Calculate {
public int add(int num1, int num2);
public int mul(int num1, int num2);
public int div(int num1, int num2);
public int sub(int num1, int num2);
}
计算机类
public class Calculator implements Calculate {
public int add(int num1, int num2) {
System.out.println("日记 :【add】 方法调用前 。参数1是:" + num1 + " , 参数2是:" + num2);
return num1 + num2;
}
public int mul(int num1, int num2) {
System.out.println("日记 :【mul】 方法调用前 。参数1是:" + num1 + " , 参数2是:" + num2);
return num1 * num2;
}
public int div(int num1, int num2) {
System.out.println("日记 :【div】 方法调用前 。参数1是:" + num1 + " , 参数2是:" + num2);
return num1 / num2;
}
public int sub(int num1, int num2) {
System.out.println("日记 :【sub】 方法调用前 。参数1是:" + num1 + " , 参数2是:" + num2);
return num1 - num2;
}
}
测试的代码
public class CalculatorTest {
public static void main(String[] args) {
Calculate calculate = new Calculator();
int result = calculate.add(12, 12);
System.out.println("相加的结果:" + result);
result = calculate.mul(12, 12);
System.out.println("相乘的结果:" + result);
}
}
上面这种方法加日记处理操作。日记的代码就会耦合到业务代码中。而且后期如果需要修改日记就需要去指的修改所有方法中的日记操作。这个维护操作非常不方便。
可以说是一个很失败的例子。
原始方法统一日记处理。
把日记的内容封装到一个类去中集中处理。
编写一个日记处理工具类
public class LogUtil {
/**
* 记录前置的日志操作
* @param method 当前运算操作
* @param args 当前运算参数
*/
public static void logBefore(String method, Object ... args){
System.out.println("操作运算是 : " + method + " 参数是 : " + Arrays.asList(args));
}
/**
* 返回日志操作
* @param method 当前方法
* @param result 当前操作返回值
*/
public static void logAfterReturning(String method, Object result){
System.out.println("当前操作运算时 : " + method + " 返回值是 : " + result);
}
/**
* 当前操作产生的异常
* @param method 当前操作
* @param e 发生的异常
*/
public static void logAfterThrowing(String method, Exception e){
System.out.println("当前运算时 : " + method + " 发生的异常是 : " + e);
}
}
修改原来Calculator中的日记代码
@Override
public int add(int num1, int num2) {
LogUtil.log("add", num1, num2);
return num1 + num2;
}
@Override
public int mul(int num1, int num2) {
LogUtil.log("mul", num1, num2);
return num1 * num2;
}
但是这种方式的不足之处是,每有一个需要加日记的类,都需要到类的代码中去添加日记功能代码。
无法做到所有对象都统一处理。
使用代理实现日记
使用jdk动态代理实现日记
创建一个计算器代理工具类
public class CalculateProxyFactory {
public static Object getProxy(final Calculate target) {
/**
* Proxy 是Jdk中自带的一个工具类(反射包下,属于反射的功能).
* Proxy类的作用: 它可以帮我们创建代理类或实例
* 方法newProxyInstance()说明: 创建代理对象实例
* 第一个参数是: 目标对象的类加载器
* 第二个参数是: 目标对象实现的所有接口
* 第三个参数是: InvocationHandler 接口的实例
* InvocationHandler 接口的实现类可以对代理的目标对象方法进行增强操作.
* 代理的目标对象 ===>>> 需要额外增加功能的类(对象实例)
* 增强操作 ===>>> 给原来功能添加的额外功能叫增强操作 ( 日记就是增强操作 )
*/
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
// 匿名内部类
/**
* invoke 方法是 InvocationHandler 接口中唯一的方法
* 代理对象每次调用方法时,都会执行 invoke() 方法 , 所有的增强操作都需要在invoke()方法中完成
* @param proxy 代理对象实例
* @param method 代理调用的方法的反射 Method 对象实例
* @param args 调用代理方法时传递进来的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理调用了 invoke 方法 ");
System.out.println(method); //打印方法信息
System.out.println(Arrays.asList(args)); //打印参数信息
// invoke() 方法执行代理对象的(加法 / 除法 / 增强日志)操作
Object result = null;
LogUtil.logBefore(method.getName(), args);
try {
// 1. 返回值是 method 方法调用时的返回值
result = method.invoke(target, args);
// 2. 增强操作
LogUtil.logAfterReturning(method.getName(), result);
}catch (Exception e){
LogUtil.logAfterThrowing(method.getName(), e);
}
// invoke() 返回代理方法的返回值
return result;
}
});
}
}
测试代码:
// 测试代码
public static void main(String[] args) {
// 目标对象
Calculate target = new CalculateImpl();
// 创建 Calculate 的代理对象实例
Calculate calculateProxy = (Calculate) createJDKProxy(target );
// jdk动态代理对象实例和目标对象实例 同宗同族 ( 他们都实现了相同的接口 )
System.out.println(calculateProxy instanceof Calculate);
System.out.println(target instanceof Calculate);
System.out.println( "代理方法的结果是 : " + calculateProxy.div(100,20) );
// jdk动态代理创建出来的代理对象实例 是 目标对象 接口的一个实现类
// 这个代理对象 和 目标对象类没有父子关系 ( 只能用接口接收代理对象 )
}
优点:这种方式已经解决我们前面所有日记需要的问题。非常的灵活。而且可以方便的在后期进行维护和升级。
缺点:当然使用jdk动态代理,需要有接口。如果没有接口。就无法使用jdk动态代理。
使用cglib代理
public class CGLibProxyFactory implements MethodInterceptor {
public static Object getCGLibProxy(Object target, Callback callback) {
// 创建一个CGLig生成器
Enhancer enhancer = new Enhancer();
// 设置父类。因为cglib是通过类,进行代码,不是通过接口
enhancer.setSuperclass(target.getClass());
// 设置拦截的代理方法
enhancer.setCallback(callback);
// create 方法创建一个代理对象并返回
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] params, MethodProxy methodProxy)
throws Throwable {
LogUtil.log(method.getName(), (int) params[0