上篇文章中,我们了解了Spring动态代理的实现和一些细节,但其实主要都是讲的如何使用。今天我们更深一步,简单说说里边的一些实现原理,和切入点表达式的一些其他写法。
一. 回顾动态代理开发的四个步骤
- 目标对象
- 额外功能
- 切入点
- 组装
目标对象不用说了,其实一般就是我们自己写的一个业务上的接口和实现。我们今天重点说说剩下的三个步骤。
关于额外功能,我们上篇文章中的额外功能,是通过实现了MethodBeforeAdvice接口来实现的,那么我们先来说说额外功能。
二. 额外功能详解
我们上篇文章中用的是MethodAdvice接口,其实我们还可以使用MethodInterceptor方法拦截器的接口来实现额外功能的开发。我们分别来看一下
2.1 MethodAdvice
我们先来看下之前是怎么写的,再来解释下参数的具体含义。
publicclassBeforeimplementsMethodBeforeAdvice{ publicvoidbefore(Methodmethod, Object[objects,Objecttarget){ System.out.println("proxy......"); } }
- before方法详解:
通过实现MethodBeforeAdvice接口,重写before方法实现。介绍下参数具体含义。
- method: 额外功能所增加给的那个原始方法,如果增加给login, 那么method就是login
- args: 额外功能所增加给的那个原始方法的参数,如果增加给login,那么args代表的就是login的参数(name, password)
- target: 额外功能所增加给的原始对象。 userServiceImpl/orderServiceImpl.
这个参数的主要目的,其实就是我们在添加额外功能的时候,如果想要对原始方法进行加工,那么可以方便我们获取到原始对象、方法和参数,当然如果我们不需要加工,就可以不使用即可。
- 三个参数在实战中如何使用
before方法的参数在实战中会根据需要进行使用,不一定都会用到,也可能都不用。
2.2 MethodInterceptor 方法拦截器
MethodInterceptor接口,也可以帮助我们实现额外功能,他相当于是一个方法拦截器,就相当于对我们的目标方法做了拦截,我们就可以在方法的前后增加一些额外功能。该接口有两个需要实现的方法。
MethodAdvice 和 MethodInterceptor对比:
MethodBeforeAdvice: 原始方法运行之前执行
MethodInterceptor: 可以根据需要,执行在方法前,也可以执行在方法后,甚至前后都可以添加额外功能
MethodInterceptor 演示:
- 目标对象, 省略,继续使用UserService, UserServiceImpl.
- 额外功能,实现MethodInterceptor接口来添加
//导入的是 aopalliance包publicclassArroundimplementsMethodInterceptor{ /*** invoke方法作用: 额外功能,原始方法之前原始方法之后原始方法之前,之后确定原始方法怎么运行?@param MethodInvocation(before中的method) : 额外功能锁增加给的那个原始方法返回值: 原始方法执行后的返回值*/publicObjectinvoke (MethodInvocationinvocation){ // 额外功能运行在原始方法执行之前System.out.println("----before----"); // 原始方法执行,获取返回值返回Objectobj=invocation.proceed(); // 额外功能运行在原始方法执行之后System.out.println("----after----"); returnobj; } } 复制代码
这中方式的好处就是把目标方法的调用由我们自己控制,通过调用invocation.proceed();方法运行,这样我们就可以自由的在目前方法前后增加我们想要的功能。
装配到bean工厂。
<beanid="arround"claa="com.xxx.Around"/>
- 定义切入点和 组装
<aop:confg><aop:pointcutid="pc"expression="execution(* *(..))"/><aop:advisoradvice-ref="around"pointcut-ref="pc"/></aop:confg>
除了在目标方法前后增加增加额外功能,我们也可以在目标方法抛出异常的时候添加额外功能。
publicObjectinvoke (MethodInvocationinvocation){ try{ Objectobj=invocation.proceed(); }catch (Throwablet){ // 添加额外功能 } returnobj; }
注意: MethodInterceptor影响方法的返回值
- 如果吧玉环市方法的返回值,直接作为invoke方法的返回值,是不会影响到原始方法的返回的
- 如果接到原始方法的返回值后做修改,就会影响。
三. 切入点表达式详解
概念: 切入点,决定了额外功能加入的位置(方法) 我们回忆下上面的案例中使用的切入点表达式的写法:
<aop:pointcutid="pc"expression="execution(* *(..))"/>
这个写法代表的是匹配所有的方法,也就是会给所有的方法都加上额外功能。所以不管是userService还是orderService在执行里边方法的时候都会执行额外功能的代码。
那切入点表达式到时代表什么含义呢?
excution: 代表切入点函数,除了execution还有其他的函数,如args等 * *(..) 代表的是切入点表达式
3.1 切入点表达式
我们以一个方法为例:
public void add (int a, int b) * * (..)
第一个* : 代表修饰符返回值
第二个*: 代表方法名
(): 代表参数列表
.. : 代表参数有没有,有几个,什么类型都可以。
举例:
- *(..) 任意修饰符返回值, 任意方法名,任意参数,作为切入点,所以所有方法都包含
- login(..) 任意修饰符返回值,方法名叫login,任意参数作为切入点,所以只切login方法
- login(String,String): 只有login方法,且两个字符串类型参数的方法会执行额外功能
- register(com.xxx.User); 和上面意思差不多,要注意类型不是java.lang要用全类名
- login(String,..) : 第一个参数 必须是String,后面不限,可有可无,有几个无所谓
3.2 精准切入点限定
如果不同包下有同名的类,同名的方法,我们应该如何处理呢。
精准的表达方式,需要加入全类名限定。 修饰符返回值 包.类.方法(参数)
修饰符返回值 包.类.方法(参数) * com.xxx.UserServiceImpl.login(..)
- 类切入点:指定特定类作为额外功能加入的位置,自然这个类中所有的方法都会加入额外功能。
修饰符返回值 包.类.方法(参数) * com.xxx.UserServiceImpl.login(..)
- 包名不同,类名相同的切入点情况
# 这种写法只能扫描一层包,只能扫描到com包下的 * *.UserServiceImpl.*(..) # 扫描多层包 * *..UserServiceImpl.*(..)
- 包切入点表达式,某个包下所有类都扫描
# 第一个星代表包下所有类,第二个星代表所有方法 * com.xxx.demo.*.*(..) 注意: 切入点中的所有类必须都在demo包下,子包不行 # 包及子包下的所有类都生效写法 * com.xxx.demo..*.*(..)
3.3 切入点函数
作用: 用于执行切入点表达式
- execution
最重要的切入点函数,功能最全 执行:方法切入点表达式, 类切入点表达式, 包切入点表达式 弊端: execution 执行切入点表达式,书写麻烦 execution(* com.demo..*.*(..)) 注意: 其他切入点函数,目的就是简化execution书写复杂度,功能上完全一致
- args
作用: 主要用于函数(方法)参数的匹配 切入点: 方法的参数必须得是两个字符串类型的参数 execution(* *(String, String)) args(String,String)
xml案例:
<bean id="arround" claa="com.xxx.Around" /> <aop:confg> <aop:pointcut id="pc" expression="args(String,String)" /> <aop:advisor advice-ref="around" pointcut-ref="pc" /> </aop:confg>
- within
主要用于类,包切入点表达式的匹配 切入点: UserServiceImpl execution(* *..UserServiceImpl.*(..)) within(*..UserServiceImpl) 包切入点: executionn(* com.xxx..*.*(..)) within(com.xxx..*)
xml案例:
<beanid="arround"claa="com.xxx.Around"/><aop:confg><aop:pointcutid="pc"expression="within(*..UserServiceImpl)"/><aop:advisoradvice-ref="around"pointcut-ref="pc"/></aop:confg>
- annocation
作用: 为有特殊注解的类或方法添加额外功能
ElementType.METHOD) (RetentionType.RUNTIME) (publicLog{ }
publicclassUserServiceImplimplementsUserService{ publicbooleanlogin(Stringname,Stringpassword){ ... } publicvoidregister(Useruser){ ... } }
<beanid="arround"claa="com.xxx.Around"/><aop:confg><aop:pointcutid="pc"expression="@annocation(com.xxx.Log)"/><aop:advisoradvice-ref="around"pointcut-ref="pc"/></aop:confg>
- 切入点函数的逻辑运算
整合多个切入点函数一起配合工作,进而完成更复杂的需求
and操作: 与操作
案例: login, 两个字符串参数作为切入点 1. execution(* login(String String)) 2. execution(* login(String,String)) and args(String,String) 注意: 与操作不能用于同种类型的切入点函数 反例 execution() and execution() 写法不行 只要 login 和 register方法,不可能同时login又叫register 用or操作 复制代码
or操作: 或操作
execution(* login(..)) or execution(* register(..))