AOP的基本概念
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知。
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用(就是要拦截的方法,在这个方法前后织入对应的通知)。
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around。
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式。
接口
public interface UserService {
void printUser(User user);
}
接口的实现类(被代理对象)
//被代理的对象
@Component
public class UserServiceImpl implements UserService {
//连接点(从动态代理角度看,就是被拦截的方法,在这个方法前后织入对应的AOP通知)
@Override
public void printUser(User user) {
System.out.println("name:"+user.getName()+" age:"+user.getAge());
}
切面类
//使用@Aspect注解一个类Spring ioc容器就会认为这是一个切面
//切面
@Aspect
public class UserAspect {
//前置通知(在被代理对象的方法前调用) *代表任意返回类型 printUser被拦截的方法 (..)任意参数
@Before("execution( * aop.UserServiceImpl.printUser(..))")
public void before(){
System.out.println("before...");
}
//后置通知(在被代理对象的方法后调用)
@After("execution( * aop.UserServiceImpl.printUser(..))")
public void after(){
System.out.println("after...");
}
//返回通知(在被代理对象的方法正常返回后调用)
@AfterReturning("execution( * aop.UserServiceImpl.printUser(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
//异常通知(在被代理对象的方法抛出异常后调用)
@AfterThrowing("execution( * aop.UserServiceImpl.printUser(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
}
AopConfig.java
@EnableAspectJAutoProxy//开启自动代理
@ComponentScan
@Configuration
public class AopConfig {
@Bean
public UserAspect getUserAspect(){
return new UserAspect();
}
}
测试
AnnotationConfigApplicationContext ioc=
new AnnotationConfigApplicationContext(AopConfig.class);
UserService uService=ioc.getBean(UserService.class);
User user=new User();
user.setName("张三");
user.setAge(18);
uService.printUser(user);
结果
before...
name:张三 age:18
after...
afterReturning...
如果认为每次都写"execution( * aop.UserServiceImpl.printUser(..))"比较麻烦可以用@Pointcut注解来定义切点
//使用@Aspect注解一个类Spring ioc容器就会认为这是一个切面
//切面
@Aspect
public class UserAspect {
//@Pointcut注解定义一个切点 *代表任意返回类型 printUser被拦截的方法 (..)任意参数
@Pointcut("execution( * aop.UserServiceImpl.printUser(..))")
public void print(){
}
//前置通知(在被代理对象的方法前调用)
@Before("print()")
public void before(){
System.out.println("before...");
}
//后置通知(在被代理对象的方法后调用)
@After("print()")
public void after(){
System.out.println("after...");
}
//返回通知(在被代理对象的方法正常返回后调用)
@AfterReturning("print()")
public void afterReturning(){
System.out.println("afterReturning...");
}
//异常通知(在被代理对象的方法抛出异常后调用)
@AfterThrowing("print()")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
}
给通知传递参数(以前置通知为例)
//前置通知(在被代理对象的方法前调用)
@Before("execution( * aop.UserServiceImpl.printUser(..))&&args(user)")
public void before(User user){
System.out.println("before..."+user.getName());
}
需要注意的是被代理对象的接口存在并不是必须的,如果有接口Spring会采用JDK动态代理,并织入对应的通知,如果不存在则使用CGLIB动态代理。
二、多切面
Spring不仅支持单切面,同样对多切面有着很好的支持。我们将接口去掉修改UserService代码,并定义多个切面。
//被代理的对象
@Component
public class UserService{
//连接点(被拦截的方法)
public void printUser(User user) {
System.out.println("name:"+user.getName()+" age:"+user.getAge());
}
}
共有三个切面分别是UserAspect1,UserAspect2,UserAspect3。切面一代码如下
//切面1
@Aspect
@Order(1)
public class UserAspect1 {
@Before("execution( * aop.UserService.printUser(..))")
public void before(){
System.out.println("before1...");
}
@After("execution( * aop.UserService.printUser(..))")
public void after(){
System.out.println("after1...");
}
@AfterReturning("execution( * aop.UserService.printUser(..))")
public void afterReturning(){
System.out.println("afterReturning1...");
}
@AfterThrowing("execution( * aop.UserService.printUser(..))")
public void afterThrowing(){
System.out.println("afterThrowing1...");
}
}
@Order(1)注解用来保证切面在织入时的执行顺序,这里传入1代表第一个执行
AopConfig.java
@EnableAspectJAutoProxy//开启自动代理
@ComponentScan
@Configuration
public class AopConfig {
@Bean
public UserAspect1 getUserAspect1(){
return new UserAspect1();
}
@Bean
public UserAspect2 getUserAspect2(){
return new UserAspect2();
}
@Bean
public UserAspect3 getUserAspect3(){
return new UserAspect3();
}
}
测试
AnnotationConfigApplicationContext ioc=new AnnotationConfigApplicationContext(AopConfig.class);
UserService userService =ioc.getBean(UserService.class);
User user=new User();
user.setName("李四");
user.setAge(20);
userService.printUser(user);
测试结果
before1...
before2...
before3...
name:李四 age:20
after3...
afterReturning3...
after2...
afterReturning2...
after1...
afterReturning1...
执行顺序图解