通常情况下,我们申明一个变量
a=1
b='abc'
c=1000000000000000000000L
d=2.3
上面我们分别申明了值为整数、字符串、长整数、浮点数的四个变量。作为解释性语言,我们可以享受这种带来的便利,想想我们的java吧,有int,float,long
当然,上面的长整型,在java中怎么办?
BigDecimal big1 = new BigDecimal("123456789012345678901234567890");
BigDecimal big2 = new BigDecimal("123456789012345678901234567890");
BigDecimal sum = big1.multiply(big2);
System.out.println(sum.toPlainString());
而在python中,就可以很简单的处理
123456789012345678901234567890L*123456789012345678901234567890L
在什么变量的时候需要注意几点
myname
和myName
不是一个标识符。注意前者中的小写n和后者中的大写N。i、__my_name、name_23
和a1b2_c3
。2things、this is spaced out
和my-name
。再来看看python中的批量命名
a, b, c = (1, 2, 3)
这里申明的a,b,c分别指向了元组[或列表]对应的元素。这叫将序列解包绑定到单独的参数上如:
name,age,address=['robin',10,('aaa',222,'ccc')]
name,age,(city,zcode,road)=['robin',10,('aaa',222,'ccc')]
需要注意变量个数和序列的个数保持一致,否则会抛出ValueError
.以下两个申明方式都有问题
a,b,c=[1,2,3,4]
a,b,c=[1,2]
这种解包不仅适用于元组,列表。对strings,files,iterators
等都可以
a,b,c,d,e='hello'
有时对序列中的多个元素只需要关注特定的几个元素,其他忽略:
_,age,_=['robin',10,('aaa',222,'ccc')]
考虑序列中多个参数,不想一一对应,而采取批量命名的方式
a,b,*c=[1,2,3,4,5] #c=[3,4,5]
a,*b,c=[1,2,3,4,5] #b=[2,3,4]
先来看两个例子
if True:
d = 10
print d #这里输出10
这里先在if
块内申明了变量d,在块外面使用,这里正确输出。而这在java中是不能这样处理的,出了if
块后申明就失效了
def fun1(a):
print a
a = 10
print a
a = 100
fun1(a)
print a
def fun2():
global b
print b
b = 10
print b
b = 100
fun2()
print b
这里包含了两个函数,其中一个变量用了global
,在函数内改变后即全局的改变,而未有任何修饰的a
则只是局部的改变。
这里有几点需要注意
def、class、lamda
if/elif/else、try/except/finally、for/while
并不能涉及变量作用域的更改,也就是说他们的代码块中的变量,在外部也是可以访问的本地变量->全局变量
看下面的函数
def fun4():
global a1
print a1
a1 = 100
print a1
a1=1000
fun4()
print a1
在函数体内,第一次print的时候并没有赋值,将会搜索本地变量,即外部定义的a1=1000
而赋值后则遵循global
左右的范围
需要明确一点,在python中没有传值一说,所有变量都是传的引用。
def method1():
a = 1;
b = a
b = 2;
print a,b
这里输出的1, 2
而不是2, 2
。来看看具体的过程
a=1
将a的地址指向内存中1的地址,b=a
将b的地址指向于a一样的内存引用,b=2
将b的地址指向新的内存地址2的引用,但a的地址引用并没有改变没问题了么?那么我们再来看看下面这个函数
def method2():
a = [1]
b = a
b[0] = 2
print a[0],b[0]
那么这个将输出什么,既然都这么问了如果再回答1, 2
肯定对不起观众了,是的输出是2, 2
?!。前面不是说了都是传引用,这是什么情况,且慢且慢
前面的问题涉及到python中的可变参数和不可变参数,而在上面的例子中恰好a,b为列表属于可变参数。在python中列表(list)、字典(dictionary)为可变参数,而元组(tuple),字符串,整形为不可变参数。实际上上面可以理解为整个列表指向了一个地址,而b[0]恰恰也操作了这个地址内的元素
再来看看下面的函数
def method3(b = []):
b.append(0)
return b
这里将列表b作为参数传给函数,我们在运行函数几次后来看看列表b的值
print method3()
print method3()
print method3()
你回答对了吗?可惜真相面前了无遁形
[0]
[0, 0]
[0, 0, 0]
说好的引用呢,每次我传递进去的参数可都是空[]
啊?都是坑啊或者我们这样调用
print method3(b=[])
print method3(b=[])
print method3(b=[])
你又答对了吗?
是不是又一次感受到了自己的无力,其实认真观察,你会发现上面的例子入参是这样b = []
,这是python函数的默认传值,如果在调用的使用没有传值那么将会使用这个默认值,比如上面的函数可以这样调用method3()
而如果函数这样申明
def method3(b):
b.append(0)
return b
那么在调用时需要指定的参数
def method3(b):
b.append(0)
return b
print method3(b=[])
print method3(b=[])
print method3(b=[])
那么这样输出”正常”了吧?再回到上面的问题,需要了解一个函数dir()
,这可以理解为我们java的内省,通过他我们可以观察到具体的申明,我们来看看上面这个method3()
做了什么
print dir(method3)
这里输出了一个列表,包含的各种属性
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
我们需要关注的是__defaults__
,让我们更详细看下调用的过程
method3()
print method3.__defaults__
method3()
print method3.__defaults__
method3()
print method3.__defaults__
而这就是__defaults__
([0],)
([0, 0],)
([0, 0, 0],)
为了对比,我们来看看另外一个函数
def method4(c, b = [], a = 1):
b.append(0)
a+=1
return b,a
method4(c=1)
print method4.__defaults__
method4(c=2)
print method4.__defaults__
method4(c=3)
print method4.__defaults__
我们可以看看其中__defaults__
的输出,这样我们可以体会可变和不可变的区别、以及默认参数与非默认参数的区别了。其实python在处理函数默认传参时如果申明了默认参数如b=[]
那么会体现在__defaults__
上
以上并不是特意选取了一些奇淫技巧,而是需要深刻理解的,因为上面的场景都是很常见的
接上文,讲解了AspectJ的两种织入方式。我们来看看用@AspectJ
标注的Aspect
的写法
@Aspect
public class LoggingAnnoAspect {
@Before("execution(* org.java.codelib..*.say*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Begin Process: " + joinPoint.getSignature().getName() + "()");
}
@AfterThrowing(pointcut = "execution(* org.java.codelib..*.method2*(..))", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println("After Throwing: " + joinPoint.getSignature().getName());
System.out.println("Exception: " + error);
System.out.println("******");
}
@Around("execution(* org.java.codelib..*.method3*(..))")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around method: " + joinPoint.getSignature().getName());
System.out.println("Method arguments: " + Arrays.toString(joinPoint.getArgs()));
joinPoint.proceed();
System.out.println("Around after is running!");
System.out.println("******");
}
}
主要通过下面两点来进行解读
这里需要用到AspectJ的一个注解org.aspectj.lang.annotation.Pointcut
,一个典型的AspectJ的声明如下
@Aspect
public class SampleAspect {
@Pointcut("execution(* org.java.codelib..*.method1*(..))")
public void method1Excution() {
}
}
这里的Pointcut包括两部分
Pointcut Expression:
这里即execution(* org.java.codelib..*.method1*(..))
规定了该Pointcut匹配规则的地方,这里即会指定包org.java.codelib包及其子包结构下方法为method1。同样这里的表达式支持几种逻辑运算(&&,||,!)
Pointcut Signature
这里的签名与通常说的方法签名类似,即method1Excution
用来承载表达式。该方法除了必须返回void签名没有限制,实际上在特定的通知情况下需要特定的签名来完成
首先来看看表达式(Pointcut Expression)的具体语法
execution
用来指定方法的签名,是使用最多的标志。语法如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param) throws-pattern)
其中方法的返回类型、参数必须要指定,如execution(public void org.package.Foo.doSomething(String))
这里指明了返回为void,以及签名为一个String类型,可以简写为execution( void doSomething(String))
同样这里也可以支持通配,主要有两种*
和..
*
用来匹配一个单词(word),例如上面可以简写为execution( * *(String))
当然这样简写后将会匹配所有的返回类型以及方法签名不仅限于前面说的doSomething
..
与前者不同的是,这个将匹配多个位置,主要用于以下两个地方execution(* org.java.codelib..*.method1*(..))
即这里会匹配org.java.codelib
包及该包下所有字包,同时也匹配方法签名0到多个,类似的还有
execution(* *(..,String))
最后一个参数必须为String
execution(* *(*,String,..))
第二个参数必须为String,剩下的类型个数不限
within
within用来声明指定类型的Joinpoint,如within(org.java.codelib.aspectj.SampleAspectJ)
那么这里只会匹配这个特定类下的方法。当然也可以支持上面说到的两种通配,其意义都一样
this和target
this和target分别指方法的调用方和被调方,通常会一起使用表示只有在this调用target的情况下符合,如this(Object2)&&target(Object3)
表示只有在Object2调用Object3时才会进行AOP拦截
args
用来捕获拥有具体参数类型,数量的方法,而不管该方法在什么类型中被声明args(org.java.sample.User)
这里将会匹配所有类中方法签名为这个指定类型的对象User
,而不管是哪个类中声明的方法。同时对于这种void method(Object obj)
如果在运行中该Object为User那么也一样匹配。
@within
@within只接受注解作为类型,用来表示拥有该注解的类型的类被匹配(注意只是类上有效,方法无效)。例子见这里
@annotation
这个和上面的@within就比较类似了,只接受注解作为类型,和上面的区别是在方法上有效。这也是应用很广泛的Pointcut了,比如我们的事务控制,通常在需要的方法加上@Transaction
更多的表达式
更多的表达式见PointcutPrimitive
怎样工作?
我们知道Pointcut需要完成的工作是根据条件找到目标的Joinpoint。所以在SpringAOP中有提供一个AspectJExpressionPointcut
其本质也是一个SpringAOP的Pointcut
,下面是相关的类图
AspectJExpressionPointcut
中完成了对上述表达式的注册、构建和方法的match。由于其本质是SpringAOP,那么是需要实现ClassFilter
和MethodMatcher
中的几个matches
方法。
我们以匹配方法来看,在AspectJExpressionPointcut
有几个重载的matches
通过代码可以发现是通过AspectJ中的PointcutExpression
来匹配执行的方法
private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
synchronized (this.shadowMapCache) {
ShadowMatch shadowMatch = (ShadowMatch) this.shadowMapCache.get(targetMethod);
if (shadowMatch == null) {
try {
shadowMatch = this.pointcutExpression.matchesMethodExecution(targetMethod);
}
catch (ReflectionWorld.ReflectionWorldException ex) {
// Failed to introspect target method, probably because it has been loaded
// in a special ClassLoader. Let's try the original method instead...
if (targetMethod == originalMethod) {
throw ex;
}
shadowMatch = this.pointcutExpression.matchesMethodExecution(originalMethod);
}
this.shadowMapCache.put(targetMethod, shadowMatch);
}
return shadowMatch;
}
}
@AspectJ形式的Advice,主要是用@AspectJ中的标注来注解Aspect中的普通方法,针对不同类型的Advice有相应的注解
>@Before
@After
@AfterReturning
@AfterThrowing
@Around
见名知意。就不再描述各个具体含义,来看一个例子
@Aspect
public class SampleAdviceAspect {
@Pointcut("execution(* *(..,String))")
//匹配任何包下的方法,其中方法的最后一个参数必须为String
private void method() {
}
@Before("method()")
public void method2() {
System.out.println("Before ...");
}
@Before("execution(* *(..,String))")
public void method3() {
System.out.println("Before do something ...");
}
}
上面用两种方式来使用了@Before
,需要注意的是method()
方法的访问权限不限。例子见这里,其他类型的通知处理方式相同。接下来看看通知中方法的参数
1、首先,在@AspectJ的Advice方法中,第一个参数默认为org.aspectj.lang.JoinPoint
,需要注意两个例外@Around为ProceedingJoinPoint
以及``
@Before("execution(* *(..,String))")
public void method4(JoinPoint joinPoint) {
joinPoint.getArgs();
}
这里通过joinPoint.getArgs()
获取了目标方法的参数
2、通过args、this、target、@annotation
等入参
@Before("execution(* *(..,String))&&@annotation(anyJoinpointAnnotation)")
public void method5(JoinPoint joinPoint, AnyJoinpointAnnotation anyJoinpointAnnotation) {
joinPoint.getArgs();
//do something with AnyJoinpointAnnotation
}
注意这里@annotation(anyJoinpointAnnotation)
中的签名必须和方法中的签名一致,或者多个
@Before(value = "execution(* *(..))&&args(name)&&@annotation(anyJoinpointAnnotation)", argNames = "name,anyJoinpointAnnotation")
public void method6(JoinPoint joinPoint, String name, AnyJoinpointAnnotation anyJoinpointAnnotation) {
System.out.println("Before: " + name);
}
不过貌似argNames
没多大用
再来看看几种特殊方式的入参方式吧
@AfterThrowing
@AfterThrowing有一个属性throwing
用来指定目标类抛出异常后改Aspect方法签名
@AfterThrowing(pointcut = "execution(* *(..))", throwing = "re")
public void logAfterThrowing(JoinPoint joinPoint, RuntimeException re) {
System.out.println("After Throwing: " + joinPoint.getSignature().getName());
System.out.println("Exception: " + re);
System.out.println("******");
}
@AfterReturning
@AfterReturning有一个returning
来获取返回的对象
@AfterReturning(pointcut = "execution(* *(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("After Process: " + joinPoint.getSignature().getName());
System.out.println("Method returned value is: " + ((User)result).toString());
System.out.println("******");
}
@Around
前面说到@Around的第一个参数默认为ProceedingJoinPoint
@Around("execution(* *(..))")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around method: " + joinPoint.getSignature().getName());
System.out.println("Method arguments: " + Arrays.toString(joinPoint.getArgs()));
joinPoint.proceed();
System.out.println("Around after is running!");
}
以上才基于注解的@AspectJ,其申明相当的简单,只需要在配置文件中
<aop:aspectj-autoproxy proxy-target-class="true"/>
当然这里true
或false
自己斟酌,本段例子
首先需要在配置文件中引入相应的XSD声明:
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
接下来看看
<aop:config>
<aop:pointcut />
<aop:advisor />
<aop:aspect />
</aop:config>
其中
<aop:config proxy-target-class="false">
`指定代理的模板为class,默认为false即代理接口,采用JDK动态代理,否则采用CGLIB代理class
<aop:advisor advice-ref="" pointcut-ref="" order=""/>
这里列举了几个属性,当然也可以用相应的schema来配置,其他类似
<aop:pointcut expression="" id=""/>
和<aop:aspect id="" ref="" order=""/>
考虑两种方式
1、BeanFactory类的转换
主要是将采用bean注册各种BeanCreator
修改为<aop:config>
如
<bean id="simpleBean" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
//
</property>
<property name="interceptorNames">
<list>
//
</list>
</property>
</bean>
来看看一个简单的配置
<aop:config>
<aop:advisor advice-ref="simpleAdvice" pointcut-ref="nameMatchMethodPointcut" />
</aop:config>
<bean id="simpleAdvice" class="org.java.codelib.spring.aop.pointcut.SimpleAdvice" />
<bean id="nameMatchMethodPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>sayHi</value>
<value>greet</value>
</list>
</property>
</bean>
<bean id="simpleBean" class="org.java.codelib.spring.aop.pointcut.SimpleBean" />
如果不想定义Pointcut且被外部访问,也可以用pointcut
属性
<aop:config>
<aop:advisor advice-ref="simpleAdvice" pointcut="execution(* *(..))" />
</aop:config>
2、@AspectJ到Schema
采用@AspectJ的Schema配置主要考虑两点:一是Aspect是普通的方法二是通过<aop:aspect...>
来配置相应的通知、Pointcut,下面是对二部分采用这样方式的简单实现
<aop:config>
<aop:aspect id="sampleAspect" ref="simpleAspectId">
<aop:pointcut expression="execution(* *(..))" id="beforePointCut" />
<aop:before method="before" pointcut-ref="beforePointCut" />
<aop:after-returning method="logAfterReturning"
pointcut-ref="beforePointCut" returning="user" />
<aop:after-throwing method="logAfterThrowing"
pointcut-ref="beforePointCut" throwing="re" />
</aop:aspect>
</aop:config>
<bean id="simpleAspectId" class="org.java.codelib.spring.aop.schema.AspectJSampleAspect" />
<bean id="simpleBean" class="org.java.codelib.spring.aop.pointcut.SimpleBean" />
最后来看看AOP很广泛的应用事务
<aop:config>
<aop:advisor pointcut="execution(* com.jd.slm.service..*Service.*(..))"
advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"
rollback-for="Throwable" isolation="READ_COMMITTED" />
<tx:method name="update*" propagation="REQUIRED"
rollback-for="Throwable" isolation="READ_COMMITTED" />
<tx:method name="delete*" propagation="REQUIRED"
rollback-for="Throwable" isolation="READ_COMMITTED" />
</tx:attributes>
</tx:advice>
从上面的配置可以看出,tx标签本质也是一个Advice,再看看XSD的描述可以发现这个TransactionInterceptor
而他实现了MethodInterceptor
前面说到ProxyFactory
是将SpringAOP中几个元素整合起来的功臣,但是我们看看他所继承的ProxyCreatorSupport
主要有三个具体的分支,用来在不同的场景:
根据名字可以看出另外两个
1、ProxyFactoryBean
容器中的织入器
2、AspectJProxyFactory
对AspectJ的支持,编程式或注解
对SpringAOP的使用,更多的时候是结合Spring容器来发挥他的优势。而这一点,我们之前介绍的ProxyFactory
更多偏向于编程式的实现。而与容器的整合这就是ProxyFactoryBean
所做的事情。
首先他继承了FactoryBean
,那么就有了作为容器的特性。我们知道FactoryBean
有一个很重要的方法来注入实现类叫
T getObject() throws Exception;
我们来看看具体的实现方法
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}
这里对singleton
属性是否设置为true
来进行分开的处理,如果为true
会通过内部的singletonInstance
进行缓存:
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
// Rely on AOP infrastructure to tell us what interfaces to proxy.
Class targetClass = getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
// Initialize the shared singleton instance.
super.setFrozen(this.freezeProxy);
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}
而这里方法getProxy
为实际获取代理对象,这个应该和之前ProxyFactory
的实现类似:
protected Object getProxy(AopProxy aopProxy) {
return aopProxy.getProxy(this.proxyClassLoader);
}
而如果为false
则每次都新建实例,具体见方法newPrototypeInstance
起底层仍然使用了上面的getProxy
方法
接下来看看具体的使用,按照之前我们编程式的方法,应该需要注入advisor、interface等
1、interceptorNames
指定植入的advisor
2、proxyInterfaces
如果是基于接口的代理,通过这个列表类型设置。另外如果没有指定有autodetectInterfaces
来自动检测接口,默认为true
3、target
设置目标target,封装为SingletonTargetSource
对象
4、proxyTargetClass
设置是否强制代理class
,默认问false
。如果为true依赖cglib
代理
下面是一个对目标类为class的进行代理了
<bean id="simpleBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<bean class="org.java.codelib.spring.aop.pointcut.SimpleBean" />
</property>
<property name="interceptorNames">
<list>
<idref bean="methodMatchPintcut" />
</list>
</property>
<property name="proxyTargetClass" value="true" />
</bean>
<bean id="methodMatchPintcut"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="simpleBeanAdvisor" />
<property name="mappedName" value="sayHi" />
</bean>
<bean id="simpleBeanAdvisor" class="org.java.codelib.spring.aop.pointcut.SimpleAdvice" />
但是这样配置是不是很繁琐,如果有多个需要代理的target该如何办?按照我们一贯的想法一般该有支持正则或是模糊匹配。是的,这个时候就需要自动化代理
>自动化代理的实现是基于BeanPostProcessor
的概念之上,在IOC容器注册完Bean后,通过扫描bean来干预其实例化
主要的自动代理的实现类有BeanNameAutoProxyCreator
、DefaultAdvisorAutoProxyCreator
和AspectJAwareAdvisorAutoProxyCreator
1、BeanNameAutoProxyCreator
<bean id="simpleBean" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<bean class="xxx.SimpleBean1" />
<bean class="xxx.SimpleBean2" />
</property>
<property name="interceptorNames">
<list>
<idref bean="methodMatchPintcut" />
</list>
</property>
</bean>
其中beanNames也可以更简洁的使用*
通配
<property name="beanNames">
<value>simple*,*Service,target*</value>
</property>
2、DefaultAdvisorAutoProxyCreator 这个属于全自动代理的范畴了,只需要在IOC容器中注册就会自动代理
这个需要详细讲解,根据名字就可以推断是对AspectJ
的自动代理。更多时候我们使用它的一个子类AnnotationAwareAspectJAutoProxyCreator
来完成我们需要的工作。关于AspectJ
和SpringAOP后面做详细的介绍。我们前面说了ProxyFactory
和容器的ProxyFactoryBean
。同样,这里也有AspectJProxyFactory
来编程式的支持,用起来是这样的:
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setProxyTargetClass(true);
proxyFactory.setTarget(new SimpleBean());
proxyFactory.addAspect(LoggingAnnoAspect.class);
SimpleBean proxyBean = (SimpleBean) proxyFactory.getProxy();
proxyBean.sayHi("Robin");
proxyBean.sayHi();
proxyBean.greet();
或者基于AnnotationAwareAspectJAutoProxyCreator
的自动代理,配置文件如下
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="simpleBean" class="org.java.codelib.spring.aop.pointcut.SimpleBean" />
<bean id="logAspect"
class="org.java.codelib.spring.aop.pointcut.aspectj.LoggingAnnoAspect" />
更多内容见这里,接下来我们先介绍AspectJ
,回头再具体介绍AnnotationAwareAspectJAutoProxyCreator
的用法
接上文,前面说了SpringAOP中Joinpoint,Pointcut,Advice的相关概念和常见类型,下面就是将这些元素整合在一起work的步骤了,这就是
## Aspect ##
在SpringAOP中,主要用Advisor来描述这一概念:
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}
作为一些常用的advisor可见UML:
1、DefaultPointcutAdvisor
这是最通用的实现,可以对除Introduction
类外的任何Pointcut、Advice来使用。使用也很简单,在构造函数的时候就可以指定:
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}
当然也有提供getter/setter方法来获取相应的Pointcut、Advice
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setAdvice(advice);
advisor.setPointcut(pointcut);
2、NameMatchMethodPointcutAdvisor
很显然,从名字来看这个是为NameMatchMethodPointcut
准备的,在实例时也只提供了对Advice
的支持,当然除了Introduction
外任何类型的advice都支持了
public NameMatchMethodPointcutAdvisor(Advice advice) {
setAdvice(advice);
}
使用这个advisor后我们就不必再去维护mapper了,因为已经有提供:
public void setMappedName(String mappedName) {
this.pointcut.setMappedName(mappedName);
}
public void setMappedNames(String[] mappedNames) {
this.pointcut.setMappedNames(mappedNames);
}
3、RegexpMethodPointcutAdvisor
与前者类似,这里也是专为regexp
准备,这里维护了一个
private AbstractRegexpMethodPointcut pointcut;
而这就涉及我们之前讲过的两种正则支持:
JdkRegexpMethodPointcut
Perl5RegexpMethodPointcut(3.1后已经没有)
用法与其他类似不再多做介绍
4、DefaultBeanFactoryPointcutAdvisor
这是与SpringIOC容器相关的advisor,功能都类似,只是需要在容器中注入。当然前面介绍的几种也可以使用容器注入的方式进行设置。
5、Order
从前面的UML图中可以看到,在顶层的抽象AbstractPointcutAdvisor
实现了Order
接口,如果存在多个Pointcut
时可以通过设定单独的order来指定执行顺序,值越小将最先被执行
前面几部分分别介绍了AOP涉及的几个部分,现在是将各个部分整合在一起工作的时候了。在SpringAOP中主要使用了ProxyFactory来完成这部分工作。以一个简单的例子来说明这个过程:
ComposablePointcut pc = new ComposablePointcut(ClassFilter.TRUE, new FooMethodMatcher());
//新增一个Pointcut:两个只需要其中一个为TRUE即可
pc.union(new BarMethodMatcher());
Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleBeforeAdvice());
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(new SimpleBean());
SimpleBean proxyBean = (SimpleBean) proxyFactory.getProxy();
proxyBean.sayHi("Robin");
proxyBean.sayHi();
proxyBean.greet();
ProxyFactory
完成的工作看起来很简单,需要将advisor
和targetbean
织入就会完成既定的工作。但我们之前说过,SpringAOP对接口和类采用了不同的代理机制,他们分别是什么,已经如何决定实际需要的factory?
我们首先来看看编程式如何完成对接口和类的代理:
1、JDK动态代理接口
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(new GreetBeanImpl());
proxyFactory.setInterfaces(new Class[] { GreetBean.class });
GreetBean proxyBean = (GreetBean) proxyFactory.getProxy();
其中GreetBean
是接口,GreetBeanImpl
是其实现类,下同
2、Cglib代理类
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(new GreetBeanImpl());
GreetBean proxyBean = (GreetBean) proxyFactory.getProxy();
要想这能跑起来需要相应的cglib
在你classpath
我们在运行时,通过日志不难发现其地处分别使用的是JdkDynamicAopProxy
和Cglib2AopProxy
。下面通过SpringAOP中的整体结构来看看其运行的原理。
在SpringAOP中,有一个AopProxy
是作为其”根”,有两个实现类来完成实际的工作,没错就是上面的两个家伙。而同时又通过工厂模式AopProxyFactory
对AopProxy
的实例进行封装:
public interface AopProxyFactory {
/**
* Create an {@link AopProxy} for the given AOP configuration.
* @param config the AOP configuration in the form of an
* AdvisedSupport object
* @return the corresponding AOP proxy
* @throws AopConfigException if the configuration is invalid
*/
AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}
AopProxyFactory
会根据传入的AdvisedSupport
来觉得用什么类型的AopProxy
,看看他的实现类DefaultAopProxyFactory
的具体方法:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
if (!cglibAvailable) {
throw new AopConfigException(
"Cannot proxy target class because CGLIB2 is not available. " +
"Add CGLIB to the class path or specify proxy interfaces.");
}
return CglibProxyFactory.createCglibProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
从上面的代码看出是根据AdvisedSupport
来判断采用何种具体的实现代理。而AdvisedSupport
又做了什么?
从上图可以看到:
1、继承了ProxyConfig
,主要描述生成代理对象的控制信息:
//代理目标对象是否为class
private boolean proxyTargetClass = false;
//代理对象是否需要进一步优化:如代理对象生成后不再修改等,如果为true会采用cglib生成代理对象
private boolean optimize = false;
//生成的代理对象是否可强制转换为Advised
boolean opaque = false;
//是否将当前生成的代理对象绑定到ThreadLocal
boolean exposeProxy = false;
//如果为true,一旦生成代理对象后将不再改变
private boolean frozen = false;
2、实现Advised
接口,用来承载生成代理对象所需要的目标类、Advice、Advisor等。从源代码可以看到这个接口定义了一些列获取、设置上述对象的方法。
回到最开始的问题,我们有个疑问在整合的时候是采用的ProxyFactory
而非上面讲述的AopProxy
或AdvisedSupport
?是的,我们来看看下图:
从上图可以很明显看到ProxyFactory
继承自ProxyCreatorSupport
,而后者提供了生成AopProxy
的方法
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
而他同时继承了AdvisedSupport
,那么我们对这个链上的都可以获取到了
上文讲述了AOP的一些基本概念,下面来讲讲SpringAOP的应用。 ## Joinpoint ## 说到Joinpoint其实需要明确的是什么样的类可以作为一个Joinpoint?在AOP的Joinpoint可以支持需要的类型:构造方法的调用、字段的设置和获取、方法的调用和方法的执行等。 但是在SpringAOP中只支持方法的执行(Method Execution)
前面说了Pointcut的作用就是用来找到系统中的Joinpot。而SpringAOP提供了一系列查找Joinpoint的方法。下面将就各个做简单的介绍,在这之前先就Spring中Pointcut做个介绍。在Spring中Pointcut
是以一个叫Pointcut
的接口来作为最顶层的抽象:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
这里ClassFilter和MethodMatcher主要用来匹配植入对象的类和方法。也就是说如果是对类匹配,我们就需要实现ClassFilter
,我们来看看:
public interface ClassFilter {
boolean matches(Class<?> clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
很明显,如果我们需要对指定的类做匹配,那么方法matches
必须返回true
,如下方法会匹配Foo:
public boolean matches(Class<?> clazz){
return Foo.class.equals(clazz);
}
接下来是MethodMatcher
,之前已经说过SpringAOP主要针对方法的执行进行,那么这个就显得特别重要了。
public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method method, Class<?> targetClass, Object[] args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
MethodMatcher
中提供了两个重载的matches
方法,这两个方法分别用在什么场景呢?很明显后者多了args
,根据我们的经验这就是方法的参数。是的,后者是可以获取方法的参数。那么怎样区分呢?这就是方法
isRuntime()
的作用,如果返回true
将调用后者,此时你能够获取方法的参数,也许你能够对方法参数进行某些检验。如:
public void doSomething(String name, int age)
比如上面的方法,传入了两个参数。你可能需要对参数做一些校验才能被执行: return "robin".equals(name) && age > 20
。而这被称为DynamicMethodMatcher
,由于每次都需要对方法参数进行校验,存在一定的效率影响,同时由于参数的不确定性也不能被缓存。
而另一种叫做StaticMethodMatcher
,主要是在isRuntime()
返回false
时被调用,这也是比较常见的情况,由于不需要每次都去校验方法参数,在很多框架内部对其进行了缓存以提高性能。
下图是常见的Pointcut
类图
下面对常见的几种Pointcut进行一些说明:
1、NameMatchMethodPointcut
根据类名可以很容易看出,这是根据方法名来与Jointcut的方法匹配,可以参考以下几种配置:
NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();
nameMatchMethodPointcut.addMethodName("sayHi");//匹配sayhi
nameMatchMethodPointcut.addMethodName("greet");//匹配greet
nameMatchMethodPointcut.setMappedName("meth*");//匹配以meth开头的方法
nameMatchMethodPointcut.setMappedNames(new String[]{"*do*", "meth*end*"});//匹配多个方法,支持简单的模糊匹配
更多实例见这里
2、JdkRegexpMethodPointcut
采用JDK的正则匹配
JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();
jdkRegexpMethodPointcut.setPattern(".*say*.*");
jdkRegexpMethodPointcut.setPatterns(new String[]{});
更多实例见这里
3、AnnotationMatchingPointcut
AnnotationMatchingPointcut通过申明相应的Annotation来进行拦截,具体可见其api说明。
首先声明了需要关注的annotation:
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorAround {
}
在测试用例中,需要指明:
//具体使用哪个构造函数根据需要来定
AnnotationMatchingPointcut pc = new AnnotationMatchingPointcut(null, MonitorAround.class);
更多实例见这里
4、ComposablePointcut
ComposablePointcut是可以对多个Pointcut进行逻辑运算的Pointcut,提供了”并”和”交”两种方式:
ComposablePointcut pc = new ComposablePointcut(ClassFilter.TRUE, new FooMethodMatcher());
//新增一个Pointcut:两个必须都满足
pc.intersection(new BarMethodMatcher());
//新增一个Pointcut:两个只需要其中一个为TRUE即可
pc.union(new BarMethodMatcher());
这里自定义了两个Pointcut
继承StaticMethodMatcher
public class FooMethodMatcher extends StaticMethodMatcher {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return "sayHi".equals(method.getName());
}
}
更多实例见这里
5、ControlFlowPointcut
ControlFlowPointcut简单的说是空值方法调用,比如有方法foo()
,只有在方法method()
调用是才被拦截,那么就是ControlFlowPointcut使用的场景了
//这里只有ControlFlowPointcutTest中的method1调用目标方法时才会进行拦截
ControlFlowPointcut controlFlow = new ControlFlowPointcut(ControlFlowPointcutTest.class, "method1");
更多实例见这里
Advice是Pointcut植入到Joinpoint处运行逻辑,通常可以区分为BeforeAdvice、AfterAdvice等。下面是UML类图
下面就几种常见的Advice做简单的介绍
1、BeforeAdvice
在SpringAOP中要实现BeforeAdvice
需要实现接口MethodBeforeAdvice
,定义如下:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object target) throws Throwable;
}
BeforeAdvice
适合在方法执行前做一些必要的处理,如权限校验、参数校验或者进行必要的初始化准备等,但是不能获取到方法的返回值的。
2、AfterReturningAdvice
SpringAop中后处理通知需要实现接口AfterReturningAdvice
,通过他我们可以获取到当前Joinpoint的返回值、方法、参数等。如下只是简单列出了方法的返回值:
public class MethodAfterAdviceSample implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After Method " + method.getName() + "(), Return "
+ (returnValue == null ? "null" : returnValue));
}
}
当然,这里是只有方法被正确执行后才会执行AfterReturningAdvice
(如果抛出异常属于后面介绍的Advice)。而且也不能更改方法的返回值,适合的场景比如在进行某些操作后做进一步的校验
3、ThrowsAdvice
SpringAop采用ThrowsAdvice
来描述AOP中的AfterThrowingAdvice
:
public interface ThrowsAdvice extends AfterAdvice {
}
在这个接口中并没有任何方法,但是在实际实现时也是可以灵活处理,只是方法名必须是afterThrowing
如:
public class AfterThrowAdviceSample implements ThrowsAdvice {
//afterThrowing([Method], [args], [target], subclassOfThrowable)
public void afterThrowing(NullPointerException exception) {
//
System.out.println("Exception: " + exception.getClass().getName());
}
public void afterThrowing(Method method, Object[] args, Object target, ClassCastException exception) {
System.out.println("Process Method: " + method.getName() + "() Exception: " + exception.getClass().getName());
}
}
这里可以重载多个afterThrowing
方法,最后的参数是捕获可能抛出的异常
4、AroundAdvice
AroundAdvice
是功能强大的一个Advice了,在SpringAop中并没有单独处理,而是直接运用了AOP Alliance中的MethodInterceptor
:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
与之前的Advice不一样的是这里方法有返回值,这就意味着可以影响Joinpoint方法的返回值。这也是应用很广泛的Advice,比如性能监控、日志记录等。
public class AroundAdviceSample implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Proceed " + invocation.getMethod().getName() + "()");
Object returnValue = invocation.proceed();
return ((Integer) returnValue) * 0.85;
}
}
这里展示了方法改变返回结果的处理,这样的场景可能是在促销时对总价打折处理