松哥原创的 Spring Boot 视频教程已经杀青,感兴趣的小伙伴戳这里-->Spring Boot+Vue+微人事视频教程
原文地址:https://www.iteye.com/blogs/subjects/springaop
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
private static ProxyFactory instance = new ProxyFactory();
private ProxyFactory() {}
public static ProxyFactory getInstance() {
return instance;
}
@SuppressWarnings("unchecked")
public <T> T create(final T t) {
return (T) Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {
/**
* 当使用创建的代理对象执行其中的方法时,都会转换为调用与代理对象绑定的InvocationHandler对象的invoke方法,
* 这样我们就可以在这个方法里面对调用情况进行一些特定的处理逻辑
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("正在调用的方法是:" + method);
//1、加入对调用方法前的处理逻辑
//...
Object result = null;
try {
//2、正常的调用目标对象的目标方法
result = method.invoke(t, args);
//3、可加入正常调用后的处理逻辑
//...
} catch (Exception e) {
//4、可加入目标对象的方法调用抛出异常后的处理逻辑
//..
} finally {
//5、可加入目标对象的方法执行完成后的处理逻辑,此逻辑不论是否抛出异常都将执行
}
return result;
}
});
}
}
以下是基于上述代码进行的一个简单示例,具体如下,有兴趣的朋友也可以自己试一试。
@Test
public void test1() {
ProxyFactory proxyFactory = ProxyFactory.getInstance();
//创建一个实现了UserService接口的对象
UserService userService = new UserServiceImpl();
//创建一个基于userService对象的代理对象
UserService proxy = proxyFactory.create(userService);
//调用代理对象的某个方法
User user = proxy.findById(1);
System.out.println(user);
}
在了解Spring Aop的用法前,我们需要先了解一下Spring Aop中的一些重要概念,这些概念的英文名称摘自Spring的官方文档,这些术语在本系列文章中出现时可能会以原始英文的形式出现。
使用Aspectj注解来实现Spring Aop时我们首先需要启用Spring对Aspectj注解支持的功能,这是通过配置来进行的。当我们的Spring配置是以配置文件为主时,我们可以通过在Spring的配置文件中引入aop相关的schema,然后通过aop:aspectj-autoproxy/来启用对Aspectj注解的支持。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.elim.test"/>
<!-- 启用对Aspectj注解的支持 -->
<aop:aspectj-autoproxy/>
</beans>
当我们的配置是通过使用@Configuration标注的配置类来进行的时候,我们就可以通过在该配置类上使用@EnableAspectJAutoProxy进行标注来启用对Aspectj注解的支持。
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
@Aspect
@Component
public class MyAspect {
}
使用@Aspect标注的切面类也可以像普通类一样定义普通的属性和方法,如果有需要也可以把它当做一个普通的bean使用。
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* add(..))")
private void pointcut() {}
}
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* add(..))")
private void pointcut() {}
@Before("com.elim.test.spring.aop.MyAspect.pointcut()")
private void before(JoinPoint joinPoint) {
System.out.println(joinPoint.getTarget() + "----------------------Before---------------------");
}
}
至此,当我们在访问Spring bean容器中任意bean对象的add方法前就会调用MyAspect切面类中定义的before方法了。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private UserService userService;
@Test
public void test1() {
User user = new User(1, "ZhangSan");
userService.add(user);
}
}
标准的Aspectj Aop的pointcut的表达式类型是很丰富的,但是Spring Aop只支持其中的9种,外加Spring Aop自己扩充的一种一共是10种类型的表达式,分别如下。
execution是使用的最多的一种Pointcut表达式,表示某个方法的执行,其标准语法如下。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern) throws-pattern?)
*
”表示所有的返回类型;*
”表示com.elim包及其子包下面的所有类型;*
”表示所有以add开头的方法名;*
)”表示带一个任意类型的参数的add方法,“add(*
,String)”则表示带两个参数,且第二个参数是String类型的add方法;*
.add*(..))”匹配所有com.elim包及其子包下所有类的以add开头的所有public方法。*
(..) throws Exception)”匹配所有抛出Exception的方法。within 是用来指定类型的,指定类型中的所有方法将被拦截。
1、“within(com.elim.spring.aop.service.UserServiceImpl)”匹配UserServiceImpl类对应对象的所有方法外部调用,而且这个对象只能是UserServiceImpl类型,不能是其子类型。2、“within(com.elim..*
)”匹配com.elim包及其子包下面所有的类的所有方法的外部调用。
Spring Aop是基于代理的,target则表示被代理的目标对象。当被代理的目标对象可以被转换为指定的类型时则表示匹配。
@target 匹配当被代理的目标对象对应的类型及其父类型上拥有指定的注解时。
1. “@target(com.elim.spring.support.MyAnnotation)”匹配被代理的目标对象对应的类型上拥有MyAnnotation注解时。
@args匹配被调用的方法上含有参数,且对应的参数类型上拥有指定的注解的情况。
@within用于匹配被代理的目标对象对应的类型或其父类型拥有指定的注解的情况,但只有在调用拥有指定注解的类上的方法时才匹配。
1、“@annotation(com.elim.spring.support.MyAnnotation)”匹配所有的方法上拥有MyAnnotation注解的方法外部调用。
bean用于匹配当调用的是指定的Spring的某个bean的方法时。
1、“bean(abc)”匹配Spring Bean容器中id或name为abc的bean的方法调用。2、“bean(user*)”匹配所有id或name为以user开头的bean的方法调用。
表达式的组合其实就是对应的表达式的逻辑运算,与、或、非。可以通过它们把多个表达式组合在一起。
1、“bean(userService) && args()”匹配id或name为userService的bean的所有无参方法。2、“bean(userService) || @annotation(MyAnnotation)”匹配id或name为userService的bean的方法调用,或者是方法上使用了MyAnnotation注解的方法调用。3、“bean(userService) && !args()”匹配id或name为userService的bean的所有有参方法调用。
在使用基于Aspectj注解的Spring Aop时,我们可以把通过@Pointcut注解定义Pointcut,指定其表达式,然后在需要使用Pointcut表达式的时候直接指定Pointcut。
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* add(..))")
private void beforeAdd() {}
@Before("beforeAdd()")
public void before() {
System.out.println("-----------before-----------");
}
}
@Before("com.elim.spring.aop.service.MyService.add()")
public void before2() {
System.out.println("-----------before2-----------");
}
当然了,除了通过引用Pointcut定义间接的引用其对应的Pointcut表达式外,我们也可以直接使用Pointcut表达式的,如下面这个示例就直接在@Before中使用了Pointcut表达式。
/**
* 所有的add方法的外部执行时
*/
@Before("execution(* add())")
public void beforeExecution() {
System.out.println("-------------before execution---------------");
}
@Before("bean(userService)")
public void before() {
System.out.println("-----before with pointcut expression: bean(userService)------");
}
注:
1、@Before除了可以通过value属性指定需要拦截的切入点外,还可以指定一个argNames属性,这个是用于方便我们在Advice中访问切入点方法参数的,这个在后续会专门用一篇文章来讲如何在Advice中使用切入点方法参数。2、argNames这个属性不仅在@Before上有,在其它的Advice注解上也有。3、除非抛出异常,否则Before Advice是没法阻止程序继续往下执行的。所有的Advice方法都可以接收一个JoinPoint参数,而且这个参数必须是Advice方法的第一个参数,通过这个参数我们可以获取到目标方法的一些信息,比如当前方法调用传递的参数信息、目标对象等。而如果是Around类型的Advice则必须接受一个ProceedingJoinPoint类型的参数,ProceedingJoinPoint是JoinPoint的子类。
@Before("bean(userService)")
public void before(JoinPoint joinPoint) {
System.out.println("-----before with pointcut expression: bean(userService)------");
joinPoint.getArgs();//获取当前目标方法调用传递的参数
joinPoint.getSignature();//获取当前目标方法的签名,通过它可以获取到目标方法名
joinPoint.getThis();//获取AOP生成的代理对象
joinPoint.getTarget();//获取被代理对象,即目标对象
System.out.println(joinPoint.getArgs());
System.out.println(joinPoint.getSignature().getName());
System.out.println(joinPoint.getThis().getClass());
System.out.println(joinPoint.getTarget().getClass());
System.out.println(joinPoint.toString());
}
@AfterReturning(value="bean(userService)", returning="returnValue")
public void afterReturning(Object returnValue) {
System.out.println("-----after returning with pointcut expression: bean(userService)------");
System.out.println("-----return value is: " + returnValue);
}
@AfterThrowing(value="bean(userService)", throwing="ex")
public void afterThrowing(Exception ex) {
System.out.println("-----after throwing with pointcut expression: bean(userService)------" + ex);
}
AfterThrowing是用于在切入点方法抛出异常时进行某些特殊的处理,但是它不会阻止方法调用者看到异常结果。
After Advice就相当于try…catch…finally语句里面的finally的角色,即无论被拦截的切入点方法是成功执行完成还是对外抛出了异常,对应的Advice处理方法都将会执行。
@After("bean(userService)")
public void after() {
System.out.println("-----after with pointcut expression: bean(userService)------");
}
@Around("bean(userService)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----around with pointcut expression: bean(userService)------");
System.out.println("---------------------调用前---------------------");
Object result = pjp.proceed();
System.out.println("---------------------调用后---------------------");
return result;
}
@Around("bean(userService)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----around with pointcut expression: bean(userService)------");
System.out.println("---------------------调用前---------------------");
Object[] params = new Object[]{15};
Object result = pjp.proceed(params);//可以调整目标方法调用时传递的参数
System.out.println("---------------------调用后---------------------");
return result;
}
@Before(value="bean(userService) && execution(* findById(java.lang.Integer)) && args(id)", argNames="id")
public void beforeWithParam(JoinPoint joinPoint, Integer id) {
System.out.println(this.getClass().getName()+" ID is : " + id);
}
@Before(value="bean(userService) && args(id)", argNames="id")
public void beforeWithParam2(int id) {
System.out.println(this.getClass().getName()+" ID is : " + id);
}
@Before(value="bean(userService) && args(id,..)", argNames="id")
public void beforeWithParam2(int id) {
System.out.println(this.getClass().getName()+" ID is : " + id);
}
@Before(value="bean(userService) && args(name, sex)", argNames="sex, name")
public void beforeWithParam3(int sex1, String name1) {
System.out.println("sex is : " + sex1);
System.out.println("name is : " + name1);
}
@Before(value="bean(userService) && args(id)")
public void beforeWithParam2(int id) {
System.out.println(this.getClass().getName()+" ID is : " + id);
}
@Before("this(userService)")
public void beforeWithParam4(IUserService userService) {
//this对象应该是一个代理对象
System.out.println(this.getClass().getName()+"==============传递this对象: " + userService.getClass());
}
我们的Advice方法可以同时接收多个目标方法参数,与此同时它也可以接收this等对象,即它们是可以混合使用的。下面这个示例中我们就同时接收了this对象和目标方法int/Interger类型的参数。
@Before("this(userService) && args(id)")
public void beforeWithParam5(IUserService userService, int id) {
System.out.println(this.getClass().getName()+"===========" + id + "==============" + userService.getClass());
}
获取target对象也比较简单,只需要把表达式改为target类型的表达式即可。
@Before("target(userService)")
public void beforeWithParam6(IUserService userService) {
System.out.println(this.getClass().getName()+"==============传递target对象: " + userService.getClass());
}
当我们的Pointcut表达式类型是通过注解匹配时,我们也可以在Advice处理方法中获取匹配的注解对象,如下面这个示例,其它如使用@target等是类似的。
@Before("@annotation(annotation)")
public void beforeWithParam7(MyAnnotation annotation) {
System.out.println(this.getClass().getName()+"==============传递标注在方法上的annotation: " + annotation.annotationType().getName());
}
public <T> void testParam(T param);
@Before("execution(void testParam(..)) && args(param)")
public void beforeWithParam8(Integer param) {
System.out.println("pointcut expression[args(param)]--------------param:" + param);
}
public interface CommonParent {
public void doSomething();
}
public class CommonParentImpl implements CommonParent {
public void doSomething() {
System.out.println("-----------do something------------");
}
}
@Component
@Aspect
public class DeclareParentsAspect {
@DeclareParents(value="com.elim.spring.aop.service..*", defaultImpl=CommonParentImpl.class)
private CommonParent commonParent;
@Before("bean(userService) && this(commonParent)")
public void beforeUserService(CommonParent commonParent) {
commonParent.doSomething();
}
}
整个过程就是这样的,非常简单。但是笔者暂时还没有发现这个在实际应用中可以应用于哪些场景,欢迎交流。
基于XML配置的Spring AOP需要引入AOP配置的Schema,然后我们就可以使用AOP Schema下定义的config、aspect、pointcut等标签进行Spring AOP配置了。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
</beans>
基于XML配置的Spring AOP的核心配置是config元素,其它的诸如切面配置、切入点配置等都是配置在它的下面的,所以我们需要先定义一个config元素。
切面的配置是通过aspect元素配置的,我们需要通过其ref属性指定切面类对应的Spring bean的id或name,还可以通过其order属性指定切面类的在拦截的时候的优先级。需要说明的是在使用Spring时基于XML的配置和基于注解的配置往往是可以混用的,所以在下面示例中我们使用的切面类引用不一定非得在Spring的bean配置文件中进行配置。
<bean id="schemaBasedAspect" class="com.elim.spring.aop.aspect.SchemaBasedAspect"/>
<!-- 基于XML的AOP配置是基于config元素开始的 -->
<aop:config>
<!-- 定义切面 -->
<aop:aspect id="aspect1" ref="schemaBasedAspect" order="1">
</aop:aspect>
</aop:config>
<aop:config>
<aop:pointcut expression="bean(userService)" id="userServicePointCut2"/>
<!-- 定义切面 -->
<aop:aspect id="aspect1" ref="schemaBasedAspect" order="1">
<aop:pointcut expression="bean(userService)" id="userServicePointCut"/>
</aop:aspect>
</aop:config>
<aop:pointcut expression="com.elim.spring.aop.aspect.SchemaBasedAspect.pointcut()" id="pointcut"/>
<aop:aspect id="aspect1" ref="schemaBasedAspect" order="1">
<!-- 定义一个Around Advice -->
<aop:before method="doBefore" pointcut-ref="userServicePointCut" />
<aop:around method="doAround" pointcut="bean(userService)"/>
<aop:pointcut expression="bean(userService)" id="userServicePointCut"/>
</aop:aspect>
<aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="returnValue"/>
<aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/>
<aop:aspect id="aspect1" ref="schemaBasedAspect" order="1">
<aop:before method="doBefore" pointcut-ref="userServicePointCut"/>
<aop:around method="doAround" pointcut="bean(userService)" />
<aop:pointcut expression="bean(userService) && args(id)" id="userServicePointCut"/>
</aop:aspect>
public void doBefore(int id) {
System.out.println("======================doBefore======================" + id);
}
public void doBefore(CommonParent commonParent) {
System.out.println("======================doBefore======================");
commonParent.doSomething();
}
<bean id="schemaBasedAspect" class="com.elim.spring.aop.aspect.SchemaBasedAspect" />
<bean id="commonParent" class="com.elim.spring.aop.service.CommonParentImpl" />
<!-- 基于XML的AOP配置是基于config元素开始的 -->
<aop:config>
<aop:aspect id="aspect1" ref="schemaBasedAspect" order="1">
<aop:before method="doBefore" pointcut-ref="userServicePointCut" />
<aop:pointcut expression="bean(userService) and this(commonParent) and !execution(void com.elim.spring.aop.service.CommonParent.doSomething())"
id="userServicePointCut" />
<!-- 加上通用的父类 -->
<aop:declare-parents types-matching="com.elim.spring.aop.service..*"
implement-interface="com.elim.spring.aop.service.CommonParent"
delegate-ref="commonParent"/>
</aop:aspect>
</aop:config>
<aop:aspectj-autoproxy proxy-target-class="true" />
如果是基于XML的配置时,我们也希望强制使用CGLIB代理时怎么办呢?我们可以在config元素上配置proxy-target-class=”true”。
<aop:config proxy-target-class="true">
</aop:config>
<aop:config>
<aop:advisor advice-ref="" pointcut-ref=""/>
</aop:config>
public class LogBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("===============before advice start==============");
System.out.println("method: " + method);
System.out.println("args: " + args);
System.out.println("target: " + target);
System.out.println("===============before advice end================");
}
}
定义了Advice之后,我们就可以通过advisor标签来把它和指定的PointCut绑定了。
<aop:config>
<aop:pointcut expression="bean(userService)" id="userServicePointcut"/>
<aop:advisor advice-ref="logBeforeAdvice" order="1" pointcut-ref="userServicePointcut"/>
</aop:config>
<bean id="logBeforeAdvice" class="com.elim.spring.aop.advice.LogBeforeAdvice" />
public class LogAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("=============================方法调用开始===" + invocation.getMethod());
try {
Object result = invocation.proceed();
System.out.println("=============================方法调用正常结束===" + invocation.getMethod());
return result;
} catch (Exception e) {
System.out.println("=============================方法调用异常===" + invocation.getMethod());
throw e;
}
}
}
public class LogAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("=============================方法调用开始===" + invocation.getMethod());
try {
Object result = invocation.getMethod().invoke(invocation.getThis(), 1);
System.out.println("=============================方法调用正常结束===" + invocation.getMethod());
return result;
} catch (Exception e) {
System.out.println("=============================方法调用异常===" + invocation.getMethod());
throw e;
}
}
}
<aop:config>
<aop:pointcut expression="bean(userService)" id="userServicePointcut"/>
<aop:advisor advice-ref="logAroundAdvice" pointcut-ref="userServicePointcut"/>
</aop:config>
<bean id="logAroundAdvice" class="com.elim.spring.aop.advice.LogAroundAdvice"/>
AfterReturning Advice将在目标方法正常返回时触发,对应的是AfterReturningAdvice接口,其定义如下,第一个参数是目标方法的返回值。
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
public class LogAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("==============调用成功返回,返回值是:" + returnValue);
System.out.println("Method: " + method);
if (returnValue instanceof User) {
//不能修改返回值,但可以修改返回值的某些属性,因为是对象引用
((User) returnValue).setName("modifyedName");
}
}
}
afterThrowing([Method, args, target], subclassOfThrowable)以下是一个实现示例,在示例中我们一共实现了三个处理Exception的方法,前两个处理方法用于处理特定的异常类型,而且只接收一个异常类型参数,最后一个处理方法接收所有的参数,处理除前两者以外的其它异常。
public class LogThrowsAdvice implements ThrowsAdvice {
/**
* 处理IllegalArgumentException
* @param e
*/
public void afterThrowing(IllegalArgumentException e) {
System.out.println("=====================方法调用异常,抛出了IllegalArgumentException");
}
/**
* 处理NumberFormatException
* @param e
*/
public void afterThrowing(NumberFormatException e) {
System.out.println("=====================方法调用异常,抛出了NumberFormatException");
}
/**
* 处理其它所有的异常
* @param method
* @param args
* @param target
* @param e
*/
public void afterThrowing(Method method, Object[] args, Object target, Exception e) {
System.out.println("=====================方法调用异常了," + e);
System.out.println("Method: " + method);
System.out.println("Args: " + args);
System.out.println("Target: " + target);
}
}
上面配置的完整配置如下。
<aop:config>
<aop:pointcut expression="bean(userService)" id="userServicePointcut"/>
<aop:advisor advice-ref="logBeforeAdvice" order="1" pointcut-ref="userServicePointcut"/>
<aop:advisor advice-ref="logThrowsAdvice" order="2" pointcut-ref="userServicePointcut" />
<aop:advisor advice-ref="logAfterReturningAdvice" order="3" pointcut-ref="userServicePointcut"/>
<aop:advisor advice-ref="logAroundAdvice" pointcut-ref="userServicePointcut"/>
</aop:config>
<bean id="logBeforeAdvice" class="com.elim.spring.aop.advice.LogBeforeAdvice" />
<bean id="logThrowsAdvice" class="com.elim.spring.aop.advice.LogThrowsAdvice" />
<bean id="logAfterReturningAdvice" class="com.elim.spring.aop.advice.LogAfterReturningAdvice" />
<bean id="logAroundAdvice" class="com.elim.spring.aop.advice.LogAroundAdvice"/>
JdkRegexpMethodPointcutSpring官方为我们提供了一个基于正则表达式来匹配方法名的Pointcut,JdkRegexpMethodPointcut。该Pointcut是继承自StaticMethodMatcherPointcut的。我们在定义JdkRegexpMethodPointcut时可以通过patterns和excludedPatterns来注入需要满足和排除的正则表达式,它们对应的都是一个String[]。比如我们想匹配所有的方法名以find开头的方法,我们可以如下定义:
<bean id="regexPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>find.*</value><!-- 所有方法名以find开始的方法 -->
</list>
</property>
</bean>
protected boolean matchesPattern(String signatureString) {
for (int i = 0; i < this.patterns.length; i++) {
boolean matched = matches(signatureString, i);
if (matched) {
for (int j = 0; j < this.excludedPatterns.length; j++) {
boolean excluded = matchesExclusion(signatureString, j);
if (excluded) {
return false;
}
}
return true;
}
}
return false;
}
RegexpMethodPointcutAdvisor使用了JdkRegexpMethodPointcut后,我们在使用的时候通常会进行如下配置。
<aop:config>
<aop:advisor advice-ref="logBeforeAdvice" pointcut-ref="regexPointcut"/>
</aop:config>
<bean id="logBeforeAdvice" class="com.elim.learn.spring.aop.advice.LogBeforeAdvice" />
<bean id="regexPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>find.*</value><!-- 所有方法名以find开始的方法 -->
</list>
</property>
</bean>
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="logBeforeAdvice"/>
<property name="pattern" value="find.*"/>
</bean>
除了可以通过注解和Xml配置定义Pointcut之外,其实我们还可以通过程序来定义Pointcut。Spring Aop的切入点(Pointcut)对应于它的一个Pointcut接口,全称是org.springframework.aop.Pointcut。该接口的定义如下:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
该接口一共定义了两个核心方法,一个用于获取该Pointcut对应的过滤Class的ClassFilter对象,一个用于获取过滤Method的MethodMatcher对象。ClassFilter接口的定义如下:
public interface ClassFilter {
boolean matches(Class<?> clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
该接口只定义了一个matches方法,用于判断指定的Class对象是否匹配当前的过滤规则。MethodMatcher接口定义如下:
public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method method, Class<?> targetClass, Object[] args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
该接口中一共定义了三个方法,两个matches方法,一个包含方法参数一个不包含。不包含方法参数的matches方法用于判断非运行时的方法匹配,比如只需要匹配方法名、方法参数定义的;包含方法参数值的matches方法用于运行时判断方法是否匹配,应用于需要根据方法传参来判断是否匹配的情况,但是该方法一般会在不包含方法参数的matches方法返回true和isRuntime()方法true的情形下才会调用。isRuntime()方法用于指定该Pointcut是否需要在运行时才能判断对应的方法是否匹配。
以下是一个自定义Pointcut的代码,其将匹配所有的名称Service结尾的Class对应的名称以find开始的方法调用:
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
/**
* 自定义Pointcut
* @author Elim
* 2017年5月8日
*/
public class MyCustomPointcut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return new MyCustomClassFilter();
}
@Override
public MethodMatcher getMethodMatcher() {
return new MyCustomMethodMatcher();
}
private static class MyCustomClassFilter implements ClassFilter {
@Override
public boolean matches(Class<?> clazz) {
//实现自己的判断逻辑,这里简单的判断对应Class的名称是以Service结尾的就表示匹配
return clazz.getName().endsWith("Service");
}
}
private static class MyCustomMethodMatcher implements MethodMatcher {
@Override
public boolean matches(Method method, Class<?> targetClass) {
//实现方法匹配逻辑
return method.getName().startsWith("find");
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object[] args) {
return false;
}
}
}
然后我们可以定义该自定义Pointcut对应的bean,再定义一个Advisor将使用该Pointcut。如下示例中我们就指定了将在MyCustomPointcut对应的切入点处采用LogAroundAdvice。
<aop:config>
<aop:advisor advice-ref="logAroundAdvice" pointcut-ref="myCustomPointcut"/>
</aop:config>
<bean id="logAroundAdvice" class="com.elim.learn.spring.aop.advice.LogAroundAdvice"/>
<bean id="myCustomPointcut" class="com.elim.learn.spring.aop.pointcut.MyCustomPointcut"/>
除了可以完全实现Pointcut接口外,我们还可以直接使用Spring自带的Pointcut。比如基于固定方法的StaticMethodMatcherPointcut。该Pointcut是一个抽象类,在使用该Pointcut时只需要实现一个抽象方法matches(Method method, Class<?> targetClass),以下是一个继承自StaticMethodMatcherPointcut的示例类定义,该Pointcut将匹配所有Class中定义的方法名以find开头的方法。
public class FindMethodMatcherPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().startsWith("find");
}
}
关于更多Spring官方已经提供的其它Pointcut定义请参考Spring的API文档。
Spring Aop是基于代理的,ProxyFactory是Spring Aop内部用来创建Proxy对象的一个工厂类。如果我们需要在程序运行时来动态的应用Spring Aop,则我们可以考虑使用ProxyFactory。使用ProxyFactory时,我们需要为它指定我们需要代理的目标对象、代理时我们需要使用的Advisor或Advice。如下示例就是一个简单的使用ProxyFactory创建MyService对象的代理,同时对其应用了一个MethodBeforeAdvice,即每次调用代理对象的方法时都将先调用MethodBeforeAdvice的before方法。
@Test
public void testProxyFactory() {
MyService myService = new MyService();
ProxyFactory proxyFactory = new ProxyFactory(myService);
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args,
Object target) throws Throwable {
System.out.println("执行目标方法调用之前的逻辑");
//不需要手动去调用目标方法,
//Spring内置逻辑里面会调用目标方法
}
});;
MyService proxy = (MyService) proxyFactory.getProxy();
proxy.add();
}
ProxyFactory有多个重载的构造函数,上面示例中笔者用的是指定被代理对象的构造函数,如果我们应用的是其它构造函数,则可以通过ProxyFactory的setTarget(Object)方法来指定被代理对象。如果我们没有指定被代理对象的Class,那么默认创建出来的代理对象是我们传递的被代理对象的类型,即获取的是targetObject.getClass()类型。如果我们的被代理对象的类型是包含多个接口实现或父类型的,而我们只希望代理其中的某一个类型时,我们可以通过ProxyFactory的setTargetClass(Class)来指定创建的代理对象是基于哪个Class的。默认情况下,ProxyFactory会根据实际情况选择创建的代理对象是基于JDK代理的还是基于CBLIB代理的,即目标对象拥有接口实现且没有设置proxyTargetClass="true"或者指定的targetClass是一个接口的时候将采用JDK代理,否则将采用CGLIB代理。也就是说即算是你通过ProxyFactory.setProxyTargetClass(true)指定了将会建立基于Class的CGLIB代理,最终也不一定是CGLIB代理,因为这种情况下如果targetClass是一个接口也将建立JDK代理。这块的逻辑是由DefaultAopProxyFactory的createAopProxy()方法实现的,其源码如下。
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);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
ProxyFactory底层在创建代理对象的时候实际上是会委托给AopProxyFactory对象的,AopProxyFactory是一个接口,其只定义了一个createAopProxy()方法,Spring提供了一个默认实现,DefaultAopProxyFactory。ProxyFactory中使用的就是DefaultAopProxyFactory,有兴趣的朋友可以参考一下ProxyFactory的源代码。
使用Aop时我们是需要对拦截的方法做一些处理的,对于Spring Aop来讲,需要对哪些方法调用做什么样的处理是通过Advisor来定义的,通常是一个PointcutAdvisor。PointcutAdvisor接口中包含主要有两个接口方法,一个用来获取Pointcut,一个用来获取Advice对象,它俩的组合就构成了需要在哪个Pointcut应用哪个Advice。所以有需要的时候我们也可以实现自己的Advisor实现。
/**
* 简单的实现自己的PointcutAdvisor
* @author Elim 2017年5月9日
*/
public class MyAdvisor implements PointcutAdvisor {
@Override
public Advice getAdvice() {
return new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args,
Object target) throws Throwable {
System.out.println("BeforeAdvice实现,在目标方法被调用前调用,目标方法是:"
+ method.getDeclaringClass().getName() + "."
+ method.getName());
}
};
}
@Override
public boolean isPerInstance() {
return true;
}
@Override
public Pointcut getPointcut() {
//匹配所有的方法调用
return Pointcut.TRUE;
}
}
@Test
public void testProxyFactory2() {
MyService myService = new MyService();
ProxyFactory proxyFactory = new ProxyFactory(myService);
proxyFactory.addAdvisor(new MyAdvisor());
MyService proxy = (MyService) proxyFactory.getProxy();
proxy.add();
}
上述示例就是一个指定代理对象对应的Advisor的示例。其实一个代理对象可以同时绑定多个Advisor对象,ProxyFactory的addAdvisor()方法可多次被调用,且该方法还有一些重载的方法定义,可以参数Spring的API文档。
我们的第一个示例指定的代理对象绑定的是一个Advice,而第二个示例指定的Advisor,对此你会不会有什么疑问呢?依据我们对Spring Aop的了解,Spring的Aop代理对象绑定的就一定是一个Advisor,而且通常是一个PointcutAdvisor,通过它我们可以知道我们的Advice究竟是要应用到哪个Pointcut(哪个方法调用)?当我们通过ProxyFactory在创建代理对象时绑定的是一个Advice对象时,实际上ProxyFactory内部还是为我们转换为了一个Advisor对象的,只是该Advisor对象对应的Pointcut是一个匹配所有方法调用的Pointcut实例。
在调用Aop代理对象的方法时,默认情况下我们是不能访问到当前的代理对象的,如果我们指定了创建的代理对象需要对外发布代理对象,那么在调用代理对象的方法时Spring会把当前的代理对象存入AopContext中,我们就可以在目标对象的方法中通过AopContext中获取到当前的代理对象了。这是通过exposeProxy属性来指定的,如果我们希望对外发布代理对象,我们可以通过exposeProxy的set方法来指定该属性的值为true。如:
@Test
public void testProxyFactory2() {
MyService myService = new MyService();
ProxyFactory proxyFactory = new ProxyFactory(myService);
proxyFactory.setExposeProxy(true);//指定对外发布代理对象,即在目标对象方法中可以通过AopContext.currentProxy()访问当前代理对象。
proxyFactory.addAdvisor(new MyAdvisor());
proxyFactory.addAdvisor(new MyAdvisor());//多次指定Advisor将同时应用多个Advisor
MyService proxy = (MyService) proxyFactory.getProxy();
proxy.add();
}
除了上述配置信息以外,ProxyFactory其实还可以配置很多其它的信息,更多的配置信息项请参考ProxyFactory的源代码或参考Spring API文档。
之前已经介绍了一款编程式的创建Aop代理的工厂——ProxyFactory,其实ProxyFactory拥有的功能AspectjProxyFactory都有。它们虽然没有直接的继承关系,但是它们都继承自ProxyCreatorSupport,而创建代理对象的核心逻辑都是在ProxyCreatorSupport中实现的。所以说ProxyFactory拥有的功能AspectjProxyFactory都有。那么AspectjProxyFactory与ProxyFactory相比有什么不同呢?AspectjProxyFactory的特殊之处就在于其可以直接指定需要创建的代理对象需要绑定的切面。在使用ProxyFactory时,我们能够绑定的是Advisor或Advice,但是如果我们的程序中已经有了现成的切面类定义且能够为我们新创建的代理类使用时,我们还要为了ProxyFactory建立代理对象的需要创建对应的Advisor类、Advice类和Pointcut类定义,这无疑是非常麻烦的。AspectjProxyFactory通常用于创建基于Aspectj风格的Aop代理对象。现假设我们有如下这样一个切面类定义。
@Aspect
public class MyAspect {
@Pointcut("execution(* add(..))")
private void beforeAdd() {}
@Before("beforeAdd()")
public void before1() {
System.out.println("-----------before-----------");
}
}
在上述切面类定义中我们定义了一个Advisor,其对应了一个BeforeAdvice,实际上是一个AspectJMethodBeforeAdvice,该Advice对应的是上面的before1()方法,还对应了一个Pointcut,实际上是一个AspectJExpressionPointcut。该Advisor的语义就是调用所有的方法名为“add”的方法时都将在调用前调用MyAspect.before1()方法。如果我们现在需要创建一个代理对象,其需要绑定的Advisor逻辑跟上面定义的切面类中定义的Advisor类似。则我们可以进行如下编程。
@Test
public void testAspectJProxyFactory() {
MyService myService = new MyService();
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(myService);
proxyFactory.addAspect(MyAspect.class);
proxyFactory.setProxyTargetClass(true);//是否需要使用CGLIB代理
MyService proxy = proxyFactory.getProxy();
proxy.add();
}
在上述代码中我们AspectjProxyFactory在创建代理对象时需要使用的切面类(其实addAspect还有一个重载的方法可以指定一个切面类的对象),其实在AspectjProxyFactory内部还是解析了该切面类中包含的所有的Advisor,然后把能够匹配当前代理对象类的Advisor与创建的代理对象绑定了。有兴趣的读者可以查看一下AspectjProxyFactory的源码,以下是部分核心代码。
public void addAspect(Class<?> aspectClass) {
String aspectName = aspectClass.getName();
AspectMetadata am = createAspectMetadata(aspectClass, aspectName);
MetadataAwareAspectInstanceFactory instanceFactory =
createAspectInstanceFactory(am, aspectClass, aspectName);
addAdvisorsFromAspectInstanceFactory(instanceFactory);
}
private void addAdvisorsFromAspectInstanceFactory(
MetadataAwareAspectInstanceFactory instanceFactory) {
List<Advisor> advisors = this.aspectFactory.getAdvisors(instanceFactory);
advisors = AopUtils.findAdvisorsThatCanApply(advisors, getTargetClass());
AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(advisors);
OrderComparator.sort(advisors);
addAdvisors(advisors);
}
需要注意的是在使用AspectjProxyFactory基于切面类创建代理对象时,我们指定的切面类上必须包含@Aspect注解。
另外需要注意的是虽然我们自己通过编程的方式可以通过AspectjProxyFactory创建基于@Aspect标注的切面类的代理,但是通过配置aop:aspectj-autoproxy/使用基于注解的Aspectj风格的Aop时,Spring内部不是通过AspectjProxyFactory创建的代理对象,而是通过ProxyFactory。有兴趣的朋友可以查看一下AnnotationAwareAspectjAutoProxyCreator的源代码。
ProxyFactoryBean实现了Spring的FactoryBean接口,所以它跟Spring中的其它FactoryBean一样,都是基于工厂模式来获取一个bean的。ProxyFactoryBean就是用来获取一个对象的代理对象的FactoryBean。它也是继承自ProxyCreatorSupport类的,所以它的功能基本跟ProxyFactory差不多,只是ProxyFactory是用于编程式的创建代理对象。而ProxyFactoryBean用于在Spring的bean容器中创建基于bean的代理对象。通常一个简单的ProxyFactoryBean配置大概会是如下这样。
<bean id="proxyFactoryBeanTestService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"><!-- 指定被代理的对象 -->
<bean class="com.elim.learn.spring.aop.service.ProxyFactoryBeanTestService"/>
</property>
<property name="proxyTargetClass" value="true"/><!-- 指定启用基于Class的代理 -->
<!-- 指定生成的代理对象需要绑定的Advice或Advisor在bean容器中的名称 -->
<property name="interceptorNames">
<list>
<value>logAroundAdvice</value>
</list>
</property>
</bean>
在上面的示例中我们被代理的对象对应的Class是com.elim.learn.spring.aop.service.ProxyFactoryBeanTestService,其是一个没有实现任何接口的Class,所以我们生成的代理对象最终会是基于CGLIB的代理。我们需要指定proxyTargetClass="true",以表示我们是倾向于使用CGLIB代理的,对于上面的配置实际上就是告诉Spring我们要使用CGLIB代理。虽然这里我们不指定proxyTargetClass="true"时,Spring可能也会给我们使用CGLIB代理,为什么这里说是可能呢?因为ProxyFactoryBean默认生成的bean都是单例、且在生成bean时会自动检测被代理对象实现的接口,而且proxyTargetClass默认是false,这种情况下ProxyFactoryBean就会自动检测被代理对象的实现的接口。按理来说我们的bean是一个没有实现接口的bean,Spring给我们去找它实现的接口是找不出来的,但是我们知道Spring的Aop是会自动为我们的对象实现一些接口的。简单的说如果我们的bean容器中配置了其它的Advisor,那么我们指定的target对象有可能就不是一个原始的bean,而是一个已经被Aop代理过的bean对象,这种bean对象,Spring Aop默认会为其实现一个Advised接口。所以使用ProxyFactoryBean时,如果我们的代理对象类是没有实现接口的,或者我们期望生成代理对象时是基于Class的,而不是基于Interface的,我们最好明确的指定proxyTargetClass="true",而不是寄希望于Spring的自动决定机制。指定被代理对象时,除了可以直接指定target外,我们还可以通过targetName指定被代理对象在bean容器中的bean名称。在上面的示例中我们通过interceptorNames属性指定了生成的代理对象需要应用的Advisor/Advice对应于bean容器中的bean的名称。跟ProxyFactory一样,如果我们指定的是Advice对象,则其会转换为一个匹配所有方法调用的Advisor与代理对象绑定。在指定intercepterNames时我们也可以通过“*
”指定所有beanName以XX开始的Advisor/Advice,如我们的bean容器中同时拥有“abc1Advisor”、“abc2Advisor”两个Advisor,我们期望创建的ProxyFactoryBean同时应用这两个Advisor,那我们可以不用单独指定两次,而是一次性把interceptorNames指定的一个beanName为“abc*
”。需要注意的是“*
”只能定义在beanName的末端。
<bean id="proxyFactoryBeanTestService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"><!-- 指定被代理的对象 -->
<bean class=" com.elim.learn.spring.aop.service.ProxyFactoryBeanTestService"/>
</property>
<property name="proxyTargetClass" value="true"/><!-- 指定启用基于Class的代理 -->
<!-- 指定生成的代理对象需要绑定的Advice或Advisor在bean容器中的名称 -->
<property name="interceptorNames">
<list>
<value>abc*</value>
</list>
</property>
</bean>
其它配置信息
我们在使用Spring Aop时,通常Spring会自动为我们创建目标bean的代理对象,以使用对应的Advisor。前提是我们在使用Spring Aop时是使用的aop:config/或aop:aspectj-autoproxy/,这是因为当我们在applicationContext.xml文件中通过aop:config/的形式定义需要使用Aop的场景时,Spring会自动为我们添加AspectjAwareAdvisorAutoProxyCreator类型的bean;而我们定义了aop:aspectj-autoproxy/时,Spring会默认为我们添加AnnotationAwareAspectjAutoProxyCreator类型的bean。
Spring中在bean实例化后能够对bean对象进行包装的是BeanPostProcessor,AspectjAwareAdvisorAutoProxyCreator和AnnotationAwareAspectjAutoProxyCreator都是实现了BeanPostProcessor接口的。
AnnotationAwareAspectjAutoProxyCreator的父类是AspectjAwareAdvisorAutoProxyCreator,而AspectjAwareAdvisorAutoProxyCreator的父类是AbstractAdvisorAutoProxyCreator,AbstractAdvisorAutoProxyCreator的父类是实现了BeanPostProcessor接口的AbstractAutoProxyCreator。
它们的核心逻辑都是在bean初始化后找出bean容器中所有的能够匹配当前bean的Advisor,找到了则将找到的Advisor通过ProxyFactory创建该bean的代理对象返回。AspectjAwareAdvisorAutoProxyCreator在寻找候选的Advisor时会找到bean容器中所有的实现了Advisor接口的bean,而AnnotationAwareAspectjAutoProxyCreator则在AspectjAwareAdvisorAutoProxyCreator的基础上增加了对标注了@Aspect的bean的处理,会附加上通过@Aspect标注的bean中隐式定义的Advisor。所以这也是为什么我们在使用@Aspect标注形式的Spring Aop时需要在applicationContext.xml文件中添加aop:aspectj-autoproxy/。
既然AspectjAwareAdvisorAutoProxyCreator和AnnotationAwareAspectjAutoProxyCreator都会自动扫描bean容器中的Advisor,所以当我们使用了aop:config/或aop:aspectj-autoproxy/形式的Aop定义时,如果因为某些原因光通过配置满足不了你Aop的需求,而需要自己实现Advisor接口时(一般是实现PointcutAdvisor接口),那这时候你只需要把自己的Advisor实现类,定义为Spring的一个bean即可。如果你在applicationContext.xml中没有定义aop:config/或aop:aspectj-autoproxy/,那你也可以直接在applicationContext.xml中直接定义AspectjAwareAdvisorAutoProxyCreator或AnnotationAwareAspectjAutoProxyCreator类型的bean,效果也是一样的。其实为了能够在创建目标bean的时候能够自动创建基于我们自定义的Advisor实现类的代理对象,我们的bean容器中只要有AbstractAutoProxyCreator类型的bean定义即可,当然了你实现自己的BeanPostProcessor,在其postProcessAfterInitialization方法中创建自己的代理对象也是可以的。
但是本着不重复发明轮子的原则,我们尽量使用官方已经提供好的实现即可。AbstractAutoProxyCreator是继承自ProxyConfig的,所以我们在定义AbstractAutoProxyCreator子类的bean时,我们也可以手动的定义一些ProxyConfig中包含的属性,比如proxyTargetClass、exposeProxy等。AbstractAutoProxyCreator的子类除了AspectjAwareAdvisorAutoProxyCreator和AnnotationAwareAspectjAutoProxyCreator外,我们可用的还有BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator。
BeanNameAutoProxyCreator可以用来定义哪些bean可与哪些Advisor/Advice绑定,以生成对应的代理对象。需要绑定的bean是通过beanNames属性来指定的,对应的是bean的名称,其中可以包含“”号,表示任意字符,比如“abc”则匹配任意名称以“abc”开始的bean;需要绑定的Advisor/Advice是通过interceptorNames来指定的,如果指定的是Advisor,那么是否可生成基于该Advisor的代理需要对应的bean能够匹配对应Advisor(PointcutAdvisor类型)的Pointcut;如果指定的是Advice,则该Advice会被包含在Pointcut恒匹配的Advisor中,即能够与所有的bean绑定生成对应的代理,且会对所有的方法调用起作用。指定interceptorNames时是不能使用通配符的,只能精确的指定需要应用的Advisor/Advice对应的bean名称。
<bean id="userService" class="com.elim.learn.spring.aop.service.UserServiceImpl"/>
<bean id="myService" class="com.elim.learn.spring.aop.service.MyService"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!-- 匹配userService和所有名称以my开头的bean -->
<property name="beanNames" value="userService, my*"/>
<!-- interceptorNames中不能使用通配符,只能是精确匹配,
即精确指定Advisor/Advice的bean名称 -->
<property name="interceptorNames" value="logBeforeAdvice, myAdvisor "/>
</bean>
<bean id="logBeforeAdvice"
class="com.elim.learn.spring.aop.advice.LogBeforeAdvice" />
<bean id="myAdvisor" class="com.elim.learn.spring.aop.advisor.MyAdvisor"/>
如上就是一个使用BeanNameAutoProxyCreator建立指定的bean基于指定的Advisor/Advice的代理对象的示例。示例中我们指定interceptorNames时特意应用了一个Advisor实现和一个Advice实现。Advice会应用于所有绑定的bean的所有方法调用,而Advisor只会应用于其中的Pointcut能够匹配的方法调用。这里的源码我就不提供了,有兴趣的朋友可以自己试试。
DefaultAdvisorAutoProxyCreator的父类也是AbstractAdvisorAutoProxyCreator。DefaultAdvisorAutoProxyCreator的作用是会默认将bean容器中所有的Advisor都取到,如果有能够匹配某一个bean的Advisor存在,则会基于能够匹配该bean的所有Advisor创建对应的代理对象。需要注意的是DefaultAdvisorAutoProxyCreator在创建bean的代理对象时是不会考虑Advice的,只是Advisor。如上面的示例中,如果我们希望所有的bean都能够自动的与匹配的Advisor进行绑定生成对应的代理对象,那么我们可以调整配置如下。
<bean id="userService" class="com.elim.learn.spring.aop.service.UserServiceImpl"/>
<bean id="myService" class="com.elim.learn.spring.aop.service.MyService"/>
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean id="myAdvisor" class="com.elim.learn.spring.aop.advisor.MyAdvisor"/>
使用DefaultAdvisorAutoProxyCreator时可能我们并不希望为所有的bean定义都自动应用bean容器中的所有Advisor,而只是希望自动创建基于部分Advisor的代理对象。这个时候如果我们期望应用自动代理的Advisor的bean定义的名称都是拥有固定的前缀时,则我们可以应用DefaultAdvisorAutoProxyCreator的setAdvisorBeanNamePrefix(String)指定需要应用的Advisor的bean名称的前缀,同时需要通过setUsePrefix(boolean)指定需要应用这种前缀匹配机制。如我们的bean容器中有两个Advisor定义,一个bean名称是“myAdvisor”,一个bean名称是“youAdvisor”,如果只期望自动创建基于bean名称以“my”开始的Advisor的代理,则可以进行如下配置。
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<property name="usePrefix" value="true" />
<!-- 匹配所有bean名称以my开始的Advisor -->
<property name="advisorBeanNamePrefix" value="my" />
</bean>
我们先来看一下基于JDK代理的JdkDynamicAopProxy的getProxy()的逻辑。
@Override
public Object getProxy() {
return getProxy(ClassUtils.getDefaultClassLoader());
}
@Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is "
+ this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils
.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) {
Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
if (specifiedInterfaces.length == 0) {
// No user-specified interfaces:
//check whether target class is an interface.
Class<?> targetClass = advised.getTargetClass();
if (targetClass != null && targetClass.isInterface()) {
specifiedInterfaces = new Class<?>[] {targetClass};
}
}
boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
boolean addAdvised = !advised.isOpaque()
&& !advised.isInterfaceProxied(Advised.class);
int nonUserIfcCount = 0;
if (addSpringProxy) {
nonUserIfcCount++;
}
if (addAdvised) {
nonUserIfcCount++;
}
Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length
+ nonUserIfcCount];
System.arraycopy(specifiedInterfaces, 0,
proxiedInterfaces, 0, specifiedInterfaces.length);
if (addSpringProxy) {
proxiedInterfaces[specifiedInterfaces.length]
= SpringProxy.class;
}
if (addAdvised) {
proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class;
}
return proxiedInterfaces;
}
我们可以看到其会在!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)返回true的情况下加上本文的主角Advised接口。isOpaque()是ProxyConfig中的一个方法,对应的是opaque属性,表示是否禁止将代理对象转换为Advised对象,默认是false。!advised.isInterfaceProxied(Advised.class)表示将要代理的目标对象类没有实现Advised接口,对于我们自己应用的Class来说,一般都不会自己去实现Advised接口的,所以这个通常也是返回true,所以通常创建Aop代理对象时是会创建包含Advised接口的代理对象的,即上述的proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class会被执行。前面我们已经提到,JdkDynamicAopProxy创建代理对象应用的InvocationHandler是其自身,所以我们在调用JdkDynamicAopProxy创建的代理对象的任何方法时都将调用JdkDynamicAopProxy实现的InvocationHandler接口的invoke(Object proxy, Method method, Object[] args)方法。该方法实现如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement
// the equals(Object) method itself.
return equals(args[0]);
}
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode()
// method itself.
return hashCode();
}
if (!this.advised.opaque
&& method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(
this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to
// minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// Get the interception chain for this method.
List<Object> chain = this.advised
.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't,
// we can fallback on direct
// reflective invocation of the target,
// and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation:
//just invoke the target directly
// Note that the final invoker must be an
// InvokerInterceptor so we know it does
// nothing but a reflective operation on the target,
// and no hot swapping or fancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target,
method, args);
} else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy,
target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType.isInstance(proxy) &&
!RawTargetAccess.class
.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and
// the return type of the method
// is type-compatible. Note that we can't help
// if the target sets
// a reference to itself in another returned object.
retVal = proxy;
} else if (retVal == null && returnType != Void.TYPE
&& returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: "
+ method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
public interface Advised extends TargetClassAware {
boolean isFrozen();
boolean isProxyTargetClass();
Class<?>[] getProxiedInterfaces();
boolean isInterfaceProxied(Class<?> intf);
void setTargetSource(TargetSource targetSource);
TargetSource getTargetSource();
void setExposeProxy(boolean exposeProxy);
boolean isExposeProxy();
void setPreFiltered(boolean preFiltered);
boolean isPreFiltered();
Advisor[] getAdvisors();
void addAdvisor(Advisor advisor) throws AopConfigException;
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
boolean removeAdvisor(Advisor advisor);
void removeAdvisor(int index) throws AopConfigException;
int indexOf(Advisor advisor);
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
void addAdvice(Advice advice) throws AopConfigException;
void addAdvice(int pos, Advice advice) throws AopConfigException;
boolean removeAdvice(Advice advice);
int indexOf(Advice advice);
String toProxyConfigString();
}
public interface Advisor {
Advice getAdvice();
boolean isPerInstance();
}
我们在使用Advisor时不会直接实现Advisor的接口,而是实现Advisor接口的子接口,PointcutAdvisor或IntroductionAdvisor。IntroductionAdvisor个人感觉用处不大,我们之前介绍的@DeclareParents和aop:declare-parents/就属于IntroductionAdvisor使用,它们对应的是DeclareParentsAdvisor。剩下的大部分应用的都是PointcutAdvisor。PointcutAdvisor接口的定义如下。
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}
public class MyAdvisor implements PointcutAdvisor {
@Override
public Advice getAdvice() {
return new MethodBeforeAdvice() {
@Override
public void before(Method method,
Object[] args, Object target) throws Throwable {
System.out.println("BeforeAdvice实现,在目标方法被调用前调用,目标方法是:"
+ method.getDeclaringClass().getName() + "."
+ method.getName());
}
};
}
@Override
public boolean isPerInstance() {
return true;
}
@Override
public Pointcut getPointcut() {
/**
* 简单的Pointcut定义,匹配所有类的find方法调用。
*/
return new Pointcut() {
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
String methodName = method.getName();
if ("find".equals(methodName)) {
return true;
}
return false;
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass,
Object[] args) {
return false;
}
};
}
};
}
}
有了自定义的Advisor后我们应该如何来应用它呢?这又区分好几种情况。
如果是自己通过编程应用ProxyFactory,或者说是应用ProxyCreatorSupport来创建代理对象,那么我们通过AdvisedSupport.addAdvisor(Advisor advisor)来应用我们自定义的Advisor。AdvisedSupport的子类中有ProxyCreatorSupport。如果我们的项目中已经应用了aop:aspectj-autoproxy/或aop:config,那么我们定义在bean容器中的Advisor bean会自动应用到匹配的bean上。这个在《Spring Aop原理之自动创建代理对象》一文中有详细介绍。如果项目中没有应用aop:aspectj-autoproxy/或aop:config,我们就需要自己定义BeanNameAutoProxyCreator、DefaultAdvisorAutoProxyCreator等AbstractAdvisorAutoProxyCreator类型的bean了。或者是定义AnnotationAwareAspectjAutoProxyCreator或AspectJAwareAdvisorAutoProxyCreator类型的bean,其实aop:aspectj-autoproxy/就是自动定义了AnnotationAwareAspectjAutoProxyCreator类型的bean,aop:config就是自动定义了AspectJAwareAdvisorAutoProxyCreator类型的bean。这样在创建bean后都会寻找匹配的Advisor建立对应的代理对象。