本文是Spring AOP的前置内容,过渡作用。备注:本文核心内容是韩顺平老师的课程,在此基础上整理的笔记和个人理解
需求
- 有一个 SmartAnimal 接口,可以完成简单的加减法, 要求在执行 getSum()和 getSub()时,输出执行前,执行过程,执行后的日志输出,请思考如何实现.
日志--方法名--getSum方法开始--参数:1.2,2.2
方法内部打印:result=3.4
日志--方法名--getSum 方法结束--结果:result=3.4
日志--方法名--getSub 方法开始--参数:1.0,2.0
方法内部打印:result=-1.0
日志--方法名--getSub 方法结束--结果:result=-1.0
传统方式实现
传统方式就是OOP,很简单,先写接口,再实现,最后测试,没有难度。
- SmartAnimalable 接口
public interface SmartAnimalable {
float getSum(float i, float j);
float getSub(float i, float j);
}
- 实现接口类 SmartDog
public class SmartDog implements SmartAnimalable{
@Override
public float getSum(float i, float j) {
System.out.println("日志--方法名--getSum方法开始--参数:"+i+","+j);
float result = i + j;
System.out.println("方法内部打印:result=" + result);
System.out.println("日志--方法名--getSum 方法结束--结果:result=" + result);
return result;
}
@Override
public float getSub(float i, float j) {
System.out.println("日志--方法名--getSub 方法开始--参数:" + i + "," + j);
float result = i - j;
System.out.println("方法内部打印:result=" + result);
System.out.println("日志--方法名--getSub 方法结束--结果:result=" + result);
return result;
}
}
- 测试
public class AopTest {
public static void main(String[] args) {
SmartDog dog = new SmartDog();
dog.getSum(1.2f, 2.2f);
dog.getSub(1, 2);
}
}
传统方式优点是实现起来简单直接,但是缺点是日志代码维护不方便,复用性很差
动态代理实现
我们在之前Spring(1)-粗解动态代理 - marigo - 博客园讲解了如何使用动态代理解决这个问题,不做过多解释直接上代码。此外,我们的getSub和getSum也有可能出现异常,所以在此还进行了异常处理,从而引出后面的横切关注点。
- MyProxyProvider
public class MyProxyProvider {
private SmartAnimalable target_obj;
public MyProxyProvider(SmartAnimalable target_obj) {
this.target_obj = target_obj;
}
public SmartAnimalable getProxy() {
// 1.获取类加载器
ClassLoader classLoader = target_obj.getClass().getClassLoader();
// 2.获取接口类型数组
Class<?>[] interfaces = target_obj.getClass().getInterfaces();
// 3.创建调用处理器对象
InvocationHandler invocationHandler = new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
String methodName = method.getName();
try {
// 在调用目标对象的方法之前,我们可以添加一些自己的操作
System.out.println("日志--方法名--" + methodName + "方法开始--参数:" + Arrays.asList(args));
// 调用目标对象的方法,反射
result = method.invoke(target_obj, args);
// 在调用目标对象的方法之后,我们可以添加一些自己的操作
System.out.println("日志--方法名:" + methodName+ "--方法正常结束--结果:result=" + result);
}catch (Exception e){
// 如果目标方法抛出异常,打印“方法异常”日志
System.out.println("日志--方法名:" + methodName+ "--方法异常结束--异常:" + e);
}finally {
// 如果目标方法正常结束或者异常结束,打印“方法结束”日志
System.out.println("日志--方法名:" + methodName+ "--方法结束");
}
return result;
}
};
// 4.创建代理对象
SmartAnimalable proxy = (SmartAnimalable) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
- SmartDog类,我们可以将其中冗余的内容删除
public class SmartDog implements SmartAnimalable{
@Override
public float getSum(float i, float j) {
float result = i + j;
return result;
}
@Override
public float getSub(float i, float j) {
float result = i - j;
return result;
}
}
- 测试
public class AopTest {
public static void main(String[] args) {
SmartAnimalable smartDog = new SmartDog();
MyProxyProvider myProxyProvider = new MyProxyProvider(smartDog);
smartDog = myProxyProvider.getProxy();
smartDog.getSum(1, 2);
smartDog.getSub(1, 2);
}
}
动态代理存在的问题:我们的输出语句功能比较弱,实际开发过程中,我们是希望通过一个方法的形式插入到执行目标方法的前后。
老韩微微一笑(土办法)
解决上面动态代理存在的问题,我们用土办法尝试解决。
需要一提的是,在前面动态代理部分其实有四个横切关注点,目标方法也就是method.invoke()
之前的部分叫前置通知,后面的是返回通知,catch处理部分是异常通知,finally部分是最终通知。那么是谁来关注,是我们的以后要学的AOP编程关注。
这四个横切关注点的日志输出功能很弱,我们将其拿下,换成方法。比如,我们对前置通知部分,拿下后,新建一个方法 before()
,参数如何确定的呢,看方法体需要什么参数就填什么。
public void before(Object proxy, Method method, Object[] args){
// 调用目标方法前打印日志
System.out.println("before 日志--方法名:" + method.getName() + "--方法开始--参数:" + Arrays.asList(args));
}
同理,后置通知拿下,换成对应的 after()
方法:
public void after(Method method, Object result){
// 调用目标方法后打印日志
System.out.println("after 日志--方法名:" + method.getName()+ "--方法正常结束--结果:result=" + result);
}
于是,前置通知和返回通知都是以方法体的形式嵌入,异常通知和最终通知也可以被替换:
比较好理解,没有难度,那么这种土方法有什么问题呢?这些方法都放在了一个类中,耦合度高。
AOP类雏形
我们需要对以上的土办法进行解耦。
- 将
before()
和after()
拿到一个类中:
public class MyAOP {
public static void before(Object proxy, Method method, Object[] args){
// 调用目标方法前打印日志
System.out.println("before 日志--方法名:" + method.getName() + "--方法开始--参数:" + Arrays.asList(args));
}
public static void after(Method method, Object result){
// 调用目标方法后打印日志
System.out.println("after 日志--方法名:" + method.getName()+ "--方法正常结束--结果:result=" + result);
}
}
- 修改 MyProxyProvider中
before()
和after()
的调用方法
InvocationHandler invocationHandler = new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
String methodName = method.getName();
try {
// 切在目标方法之前
MyAOP.before(proxy, method, args);
// 调用目标对象的方法,反射
result = method.invoke(target_obj, args);
// 切在目标方法之后
MyAOP.after(method, result);
}catch...
}
};
由此,一个简单得不能简单的AOP雏形产生了:写了一个类,其中有很多静态方法,这些静态方法再切入到其他某个类的方法中。
当然这还算不上AOP,存在很多问题:不够灵活,复用性差,比如SmartDog类中有两个方法,如果before或者after方法只对其中一个类进行控制就显得不顺。
我们后续会对Spring AOP进行更具体地讲解。