切点的定义规则
重点:
基本语法格式:execution(修饰符 返回类型 类描述 方法名 参数类型 异常),除了返回类型、方法名和参数,其他都是可以省略的。
例如:execution(public * com.sample.service.impl….(…) throws java.lang.IllegalAccessException)
符号 | 含义 |
---|---|
execution() | 表达式的主体 |
public | 方法的修饰符 |
第一个* | 任意类型的返回值 |
com.sample.service.impl | 描述包名 |
… | 表示当前包及子包,如果是一个.就表示当前包不包括子包 |
第二个* | 表示类名 |
.*(…) | 表示任何方法名任意参数,…换成*表示匹配只有一个任意参数的方法 |
throws | 匹配方法声明抛出的异常 |
(com.sample.service.impl…*.)也是可以省略的,表示任意类。如“execution(public * *(…))”,表示匹配任意方法
例子 | 作用 |
---|---|
within(com.service…*) && args(Long) | 匹配service包下的方法和匹配任何只有一个Long参数的方法 |
within(com.service…*) && args(Long,…) | 匹配service包下的方法和匹配第一个参数为Long型的方法 |
within(com.service.UserService) | 匹配UserService类中所有的方法 |
within(com.service.UserService+) | 匹配UserService类及子类的所有方法 |
within(com.service.*) | 匹配service包下所有类中所有方法(不包括子包) |
within(com.service…*) | 匹配service包下所有类中所有方法(包括子包) |
args(Object) | 匹配运行时入参是Object类型或其子类型的方法 |
this(com.Dao) | 匹配当前AOP代理对象类型的执行方法 |
target(com.IDao) | 匹配实现IDao接口的目标对象的方法 |
bean(*Service) | 匹配所有以Service结尾的bean |
bean(HelloService) | 匹配指定的bean |
@annotation(com.MyAnnotation) | 匹配标注有MyAnnotation注解的方法(注意是方法) |
@within(com.MyAnnotation) | 匹配使用了MyAnnotation注解的类(注意是类) |
@target(com.MyAnnotation) | 匹配标注有MyAnnotation注解的类及其子类的方法(runtime级别) |
@args(com.MyAnnotation) | 匹配传入的参数类标注有MyAnnotation注解的方法 |
切点指示符可以使用运算符语法表达式&&、||、!,如果使用XML的方式引入可以用and、or、not代替。
为什么要代替呢?因为&在XML中具有特殊的含义。
一个切面
需要增强的接口:
public interface IService {
int addUser(String name);
void updateUser(String name);
}
//因为要进行切面代理,必须交给Spring容器管理
@Component
public class ServiceImpl implements IService {
@Override
public int addUser(String name) {
System.out.println("add user " + name);
return 6666;
}
@Override
public void updateUser(String name) {
System.out.println("update user " + name);
}
}
定义切面:
//定义一个切面,并当成Bean交由spring管理
@Aspect
@Component
public class MyAspect {
//定义切点函数
@Pointcut(value = "execution(* aop.spring.aspect.service.IService.*User(..)) && args(name)")
private void myPointcut(String name) {
}
//环绕通知,value属性写spring表达式或者pointcut名,
//ProceedingJoinPoint是spring提供的静态变量,可以获取目标对象的信息,方法的参数要与argNames的一致
@Around(value = "execution(* addUser(..)) && args(name)", argNames = "joinPoint,name")
private Object around(ProceedingJoinPoint joinPoint, String name) throws Throwable {
System.out.println("name:" + name);
//获取切入点的参数
Object[] objects = joinPoint.getArgs();
for (Object o : objects) {
System.out.println("args:" + o);
}
System.out.println("around_before:");
//执行目标方法
Object object = joinPoint.proceed();
System.out.println("around_after:");
return 333;
}
//前置通知,目标函数执行前执行
@Before(value = "myPointcut(name)")
public void a_before(String name) {
System.out.println("a_before:" + name);
}
//异常通知,方法抛出异常才会执行,异常参数需要throwing属性指定
@AfterThrowing(value = "execution(* aop.spring.aspect.service.IService.addUser(..))", throwing = "e")
public void afterThrowable(Throwable e) {
System.out.println("afterThrowing:" + e.getMessage());
}
//最终通知,即使抛出了异常也会执行
@After(value = "execution(* aop.spring.aspect.service.IService.addUser(..))")
public void a_after() {
System.out.println("a_after:");
}
//后置通知,在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,
//当目标函数没有返回值时,returnVal将返回null,
//必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同
//如果配置了环绕通知,那么这个returning属性就是环绕通知的返回值
@AfterReturning(value = "execution(* aop.spring.aspect.service.IService.addUser(..))", returning = "returnVal")
public void AfterReturning(Object returnVal) {
System.out.println(returnVal);
}
}
各个通知的执行顺序大致如下:
try{
around_before
before
around_after
return afterReturning
}catch(Throwable e){
afterThrowing
}finally{
after
}
要想配置的切面能够生效,仅仅配置了@Aspect还不够,需要在配置文件中加上<aop:aspectj-autoproxy/>
。
可以在@Configuration中用@EnableAspectJAutoProxy代替。
注意:一个切面(aspect)内如果有多个相同的通知advise(例如都是前置通知),对应一个切点(pointcut),那么相同的通知函数执行的顺序按通知函数方法名的字符串大小判断,a()优先于b()。
多个切面
配置多个切面(只是改个数字而已):
@Aspect
@Order(2)
public class Aspect1 {
@Pointcut(value = "execution(* aop.spring.aspect.service.IService.*User(..)) && args(name)")
private void myPointcut(String name) {
}
@Before(value = "myPointcut(name)")
public void before(String name) {
System.out.println("before1:" + name);
}
@After(value = "myPointcut(name)")
public void after(String name) {
System.out.println("after1:" + name);
}
@AfterThrowing(value = "myPointcut(name)", throwing = "e")
public void afterThrowing(String name, Throwable e) {
System.out.println("afterThrowing1:" + name + " e");
}
@AfterReturning(value = "myPointcut(name)", returning = "returnVal")
public void afterReturning(String name, Object returnVal) {
System.out.println("afterReturning1:" + returnVal);
}
}
@Aspect
@Order(1)
public class Aspect2
@Aspect
@Order(3)
public class Aspect3
注解配置:
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "aop.spring.aspect.service")
public class MultiConfig {
@Bean
public Aspect1 c() {
return new Aspect1();
}
@Bean
public Aspect2 a() {
return new Aspect2();
}
@Bean
public Aspect3 b() {
return new Aspect3();
}
}
测试:
public class OrderTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MultiConfig.class);
IService iService = (IService) context.getBean("serviceImpl");
iService.addUser("ljh");
}
}
多个切面之间按照定义的顺序从上到下执行,也可以通过@Order
注解配置。Order注解的属性value越小,优先级越高。
未加@Order注解时的输出:
before1:ljh
before2:ljh
before3:ljh
add user ljh
after3:ljh
afterReturning3:6666
after2:ljh
afterReturning2:6666
after1:ljh
afterReturning1:6666
说明是按定义的顺序执行。
加了@Order注解并设值的输出:
before2:ljh
before1:ljh
before3:ljh
add user ljh
after3:ljh
afterReturning3:6666
after1:ljh
afterReturning1:6666
after2:ljh
afterReturning2:6666
说明按设置的顺序执行。
从输出还可以看出after的通知是按照责任链模式的,和spring中多个拦截器的执行顺序类似。
引入
现在我们已经给代理类实现了切面控制,那么如果我想再给代理类添加一个接口(不能是类)呢?可以使用@DeclareParents注解实现。JDK动态代理本来就是通过代理接口实现的,现在无非就是在多代理一个接口而已。如果是CGLIB动态代理,一般情况下是代理类的(只能代理一个类),但是也可以代理接口,因此CGLIB也可以完成。
public interface IVerifier {
boolean verify();
}
public class Verifier implements IVerifier {
@Override
public boolean verify() {
System.out.println("verify");
return false;
}
}
@Aspect
@Component
public class MyAspect {
//value表示对ServiceImpl及其子类引入新的接口,value属性值不能是接口,Spring选择使用何种代理是按照实现类是否实现接口判断的
//defaultImpl代表引入接口的默认实现类
@DeclareParents(value = "aop.spring.aspect.service.ServiceImpl+", defaultImpl = Verifier.class)
//必须是接口,JDK动态代理只能代理接口,CGLIB只能代理一个类
public IVerifier iVerifier;
}
测试:
public class Test {
public static void main(String... args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring_aspectTest.xml");
IVerifier iVerifier = applicationContext.getBean(IVerifier.class);
iVerifier.verify();
}
}
//output:
verify