切面

切面需要实现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,为什么两次结果会不一样呢?

先来看一下这两次调用的调用时机:

  1. 第一次调用时,匹配方法,如果匹配上了就会生成代理对象,Method所在的类为com.morris.spring.service.UserServiceImpl。
  2. 第二次调用时,调用代理对象的方法时会再次匹配,因为有的方法不需要代理,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()才会执行(这个方法只会执行一次,真正调用时才会知道参数的具体值):

  1. 不带方法参数的matches()返回true。
  2. isRuntime()返回true。