Spring AOP 编程思想---深入浅出


一、为一个方法增加额外功能引起的思考

额外功能指在核心业务实现上拓展出的一些辅助功能,比如日志、性能监测等。

我们先来看如下案例:

class UserServiceImpl implements UserService{
	public boolean login(String name, String passWord) {
	        System.out.println("***** login *****");
	        return true;
	    }
}

需求:
login方法中增加记录日志的功能

常规设计:

class UserServiceImpl implements UserService{
	public boolean login(String name, String passWord)  {
			//这里简化了日志写法,只为了传达思想
			System.out.println("--- log ---");
	        System.out.println("***** login *****");
	        return true;
	    }
}

运行结果:
在这里插入图片描述

从运行结果来看,我们达到了预期的设计要求,在原始方法基础上添加了打印日志的额外功能。

思考:
日志与核心业务放在一块编码,是否存在耦合性问题?是否存在暴露核心业务问题?
如果想增加额外功能,那么就要修改代码,这样是否会降低维护性?
显然上述问题的答案都是肯定的,直接在核心业务上加入额外功能确实不是一个优秀的设计思想

二、代理设计模式

带着上述问题,我们来学习代理设计模式,在学习之前,先引入生活中房东和顾客的案例。

房客作为消费者,想买房
房东作为房屋出租者,负责签合同,收钱
二者的矛盾在于房客想要进一步获得房子的信息,而房东只想签合同,不想干其他事
这里房客想要进一步获得房子信息就是额外功能的体现
而房东又不想实现这个额外功能
所以引入第三个类,中介类
中介在自己的方法体内定义了额外功能,并通过调用房东的方法也实现了签合同付钱的功能
中介的出现可谓解决了二者之间的矛盾

在这里插入图片描述
那么回到我们的需求,借鉴现实生活中的解决方式,我们也提出了一个类似中介一样的中间类,下面我们继续梳理下这个中间类应该实现哪些功能?

  1. 需要实现和核心业务类相同的接口,确保核心业务类实现的方法都在中间类中被实现
  2. 编写额外的功能

带着上述两点总结,我们实现下这个中间类。

class UserServiceProxy implements UserService{
    private UserServiceImpl userService = new UserServiceImpl();

    @Override
    public boolean login(String name, String passWord) {
        System.out.println("--- log ---");
        return userService.login(name, passWord);
    }
}

运行结果:
在这里插入图片描述
上述的设计思想就是代理模式设计思想,我们提到的中间类即代理类,需要指出的是,我们所指的是静态代理类。该设计模式解决了我们在前面提到的关于耦合性的问题。

小结:
代理类的优点:

  1. 代理模式能将代理对象与真实被调用的目标对象分离。

  2. 一定程度上降低了系统的耦合度,扩展性好。

  3. 可以起到保护目标对象的作用。

  4. 可以对目标对象的功能增强。

思考:
一个项目中的核心业务往往很多,如果我们对每个核心业务都通过静态代理模式来扩展额外功能的话,就相当于增加了一倍数量的类,不仅大量增加了静态类文件数量导致项目不易管理,同时也使得项目维护性差,因为如果修改额外功能的话,对应的代理类都需要修改。

三、动态代理

动态代理的整体思路和静态代理是吻合的,都是通过一个第三方代理类来增加额外功能,但是区别在于静态代理类是我们手动创建的,而动态代理类采用了动态字节码生成技术(跳过.java文件,直接生成.class文件),在JVM运行过程中动态创建代理类,这样就解决了静态代理造成的大量静态文件生成的问题。

1.如何进行动态代理

1.1 JDK动态代理

回顾JVM创建一个普通类的过程:
虚拟机将.java文件通过类加载机制编译成.class文件,通过类的信息在堆区实例化一个对象,并在栈帧中创建对应的引用指向该对象。

而动态代理类显然是没有.java文件的,因为我们并没有对动态代理类有过任何编码,显然如何生成.class文件是动态代理的核心思想。

JDK动态代理的核心思想是如下API:

Proxy.newProxyInstance(classloader, interfaces, invocationhandler);

classloader表示类加载器,毫无疑问任何想要创建对象的动作都必须有类加载器,然而由于缺少.Java文件,所以无法直接定位到某个加载器,所以这里的classloader主要表示调用别的类的加载器,由双亲委派模型我们知道只要调用同一级别或者更下面的类加载器也是可以加载自己的。

interfaces表示接口,这里的接口是和原始类对应的,在静态代理类中我们强调原始类和代理类应该具有相同的接口,动态代理也不例外。

invocationhandler是整个动态代理的核心思想。
作用:用于书写额外功能,额外功能可以运行在原始方法执行前/执行后/执行前后/抛出异常时。
核心方法:接口重载了invoke方法

Object invoke(Object proxy, Method method, Object[] args){
	//...
}

参数解读:

  1. 返回值Object指的是原始方法的返回值
  2. proxy指的是代理对象,这个我们一般用不到
  3. args表示原始方法的参数
  4. method表示实现的额外方法

method中有一个invoke方法,起到方法拦截的作用:

//传入原始方法的对象,方法参数
Object invoke(Object obj, Object.. args){
	//...
	//
	Object obj = invocation.proceed();
	
	return obj;
}

相当于调用了一次原始方法。

有了上述参数,我们就可以考虑向invocationhandler中的invoke方法写入具体的内容。
具体思路和静态代理的思路类似,在方法中调用原始方法,在原始方法执行前后加入额外功能。

Object invoke(Object proxy, Method method, Object[] args){
	System.out.println("***** log *****");
	Object ret = method.invoke(UserService, args);

	return ret;
}

这样该接口就实现了额外功能注入的功能。

此时我们介绍完了所有的参数,下面将JDK动态代理一次完整的调用方法补全:

//JDK创建动态代理
InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("******* proxy log *******");
        //原始方法运行
        Object obj = method.invoke(userService, args);
        return obj;
    }
};
UserService userService1 = (UserService) Proxy.newProxyInstance(TestJDK.class.getClassLoader(), userService.getClass().getInterfaces(), handler);

此时返回的userService1就是动态代理对象,包含了原始方法和额外功能。

1.2 CGlib动态代理

CGlib动态代理方式和JDK动态代理方式基本吻合,只不过JDK动态代理保证了动态代理于原始类实现相同的接口,而CGlib动态代理主要针对原始类没有实现接口的情况,通过继承原始类达到实现所有原始方法的目的。

public static void main(String[] args) {
    //1.创建原始对象
    UserService userService = new UserService();
    //2.通过cglib创建动态代理对象
    Enhancer enhancer = new Enhancer();
	//等同于JDK动态代理的类加载器
    enhancer.setClassLoader(TestCglib.class.getClassLoader());
    等同于JDK动态代理的接口
    enhancer.setSuperclass(userService.getClass());
    //等同于JDK动态代理的invocationhandler接口
    MethodInterceptor interceptor = new MethodInterceptor() {
        //等同于invoke
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("--- cglib log ---");
            Object obj = method.invoke(userService, args);
            return obj;
        }
    };

    enhancer.setCallback(interceptor);
    UserService userService1 = (UserService) enhancer.create();
}

2.Spring工厂如何创建代理类对象

目前为止,我们了解了如何手动创建动态代理,但是Spring是如何创建动态代理的呢?

在了解Spring创建动态代理前,我们应该先了解Spring的对象生命周期,探索一个对象在Spring中从创建到销毁的过程。

以下是Bean生命周期简化版本,只为了突出动态代理在Spring中的实现:
在这里插入图片描述

简单总结如下:

  1. 通过工厂创建对象
  2. 将对象传给前置BeanPostProcessor接口,进行前置加工
  3. 将对象初始化
  4. 将对象传给后置BeanPostProcessor接口,进行后置加工,在该步骤中调用动态代理加工对象,返回动态代理类
  5. 将代理类传递给调用者,即调用者通过原始类id调用的是加工后的动态代理类

也就是说,动态代理是在后置加工阶段完成的,即动态代理是将原始类加工获得的,经加工后,也就无法再定位到原始类,或者说原始类变成了动态代理类,这也起到了进一步封装保护的作用。

通过后置加工生成动态代理类的编码如下:

public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("--- new log ---");
                Object obj = method.invoke(bean, args);
                return obj;
            }
        };

        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
    }
}

创建完编码,默认是对所有方法都执行该后置加工的,即为所有原始方法都增加了额外功能,那么如何将后置加工应用于特定的方法呢?这就要引入切入点的概念。

切入点决定了额外功能作用的原始方法

<aop:pointcut id="pc" expression="execution(* *(..))"/>

exection(* *(..))表示匹配所有方法
切入点函数—execution()
切入点表达式—* *(..)

具体的切入点表达式语法及其他写法不在本文赘述。

上述就是AOP编程的核心思想
AOP:面向切面编程
切面:切入点+额外方法
也可以说AOP编程就是Spring动态代理开发

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值