切面
切面需要实现PointcutAdvisor接口,包含切点和通知。
package com.morris.spring.aop;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
@SuppressWarnings("serial")
public class UserServiceAdvisor extends AbstractPointcutAdvisor {
@Override
public Advice getAdvice() {
return new UserServiceAdvice();
}
@Override
public Pointcut getPointcut() {
return new UserServicePointcut();
}
}
切点
切点需要实现Pointcut,里面要实现对类的匹配和方法的匹配。
这里不对类进行校验,所以使用Spring内部的ClassFilter.TRUE。
对方法的匹配需要实现MethodMatcher,所以UserServicePointcut同时实现了MethodMatcher和Pointcut。
package com.morris.spring.aop;
import com.morris.spring.annotation.Abcd;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AopUtils;
import java.lang.reflect.Method;
public class UserServicePointcut implements MethodMatcher, Pointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
if(method.getName().equals("query")) {
return true;
}
return false;
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher() {
return this;
}
}
通知
通知主要是对目标方法的增强,这里只是打印。
package com.morris.spring.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class UserServiceAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println(invocation.getMethod().getName()+"-------------------------");
return invocation.proceed();
}
}
测试类
package com.morris.spring.demo.annotation;
import com.morris.spring.aop.UserServiceAdvisor;
import com.morris.spring.service.UserService;
import com.morris.spring.service.UserServiceImpl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy // 开启AOP
public class CustomerAspectDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(UserServiceImpl.class);
applicationContext.register(UserServiceAdvisor.class); // 自定义切面
applicationContext.register(CustomerAspectDemo.class); // 开启AOP
applicationContext.refresh();
UserService userService = applicationContext.getBean(UserService.class);
userService.query("hello");
}
}
运行结果如下:
query-------------------------
UserServiceImpl hello
发现通知中的增强代码执行了。
尝试获得目标方法上的注解信息
UserServiceImpl.query()方法上面加上@Abcd(“abcd”)
注解:
public class UserServiceImpl implements UserService {
@Abcd("abcd")
@Override
public void query(String name) {
System.out.println("UserServiceImpl " + name);
}
}
com.morris.spring.aop.UserServicePointcut#matches(java.lang.reflect.Method, java.lang.Class<?>)
public boolean matches(Method method, Class<?> targetClass) {
if(method.isAnnotationPresent(Abcd.class)) {
return true;
}
return false;
}
运行结果会发现通知中的增强代码并没有被执行,这是为什么呢?目标方法query()上面明明有@Abcd注解,为什么获取不到呢?
在matches()方法中打上断点,会发现这个方法会被调用两次,第一次会返回true,第二次返回false,为什么两次结果会不一样呢?
先来看一下这两次调用的调用时机:
- 第一次调用时,匹配方法,如果匹配上了就会生成代理对象,Method所在的类为com.morris.spring.service.UserServiceImpl。
- 第二次调用时,调用代理对象的方法时会再次匹配,因为有的方法不需要代理,Method所在的类为com.morris.spring.service.UserService。
第二次调用时Method是来自于UserService接口,而接口上面的方法是没有注解的,这点也可以从动态代理生成的类中看出:
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
// 来自接口
m3 = Class.forName("com.morris.spring.service.UserService").getMethod("query", Class.forName("java.lang.String"));
m4 = Class.forName("com.morris.spring.service.UserService").getMethod("hi", Class.forName("com.morris.spring.entity.D"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
而UserService.query()方法上面并没有@Abcd注解,那么要怎么样才能获得注解的信息呢?Spring提供了下面的工具类:
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if(specificMethod.isAnnotationPresent(Abcd.class)) {
return true;
}
方法参数级别更细粒度的匹配
MethodMatcher还有两个方法:
boolean isRuntime();
boolean matches(Method method, Class<?> targetClass, Object... args);
这两个方法配合使用能在运行时对参数的值进行匹配。
public boolean isRuntime() {
return true;
}
public boolean matches(Method method, Class<?> targetClass, Object... args) {
if(null != args && null != args[0] && "morris".equals(args[0])) {
System.out.println("matches args");
return true;
}
return false;
}
需要满足两个条件这个带方法参数的matches()才会执行(这个方法只会执行一次,真正调用时才会知道参数的具体值):
- 不带方法参数的matches()返回true。
- isRuntime()返回true。