@Aspect 一个简单的注解式 spring Aop demo
前言
使用spring aop,我们可在具体的执行方法前后,再执行点别的东西;比如spring的事务管理,就使用了这种机制。
接下来,先使用springboot创建个简单的web项目;
创建项目
新建一个service,和实现类,
public interface HelloService {
void printHello();
}
@Service
public class HelloServiceImpl implements HelloService {
@Override
public void printHello() {
System.out.println("hello,我是被aop的方法,printHello");
}
}
然后新建一个测试类,
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloServiceTest {
@Resource
private HelloService helloService;
@Test
public void printHello() {
helloService.printHello();
}
}
先跑一下测试类,输出:
hello,我是被aop的方法,printHello
AOP
1.编写切面
@Aspect
public class HelloAspect {
@Before("execution(* com.example.demo.services.impl.HelloServiceImpl.printHello(..))")
public void before(){
System.out.println("before------");
}
@After("execution(* com.example.demo.services.impl.HelloServiceImpl.printHello(..))")
public void after(){
System.out.println("after------");
}
@AfterReturning("execution(* com.example.demo.services.impl.HelloServiceImpl.printHello(..))")
public void afterReturning(){
System.out.println("afterReturning------");
}
@AfterThrowing("execution(* com.example.demo.services.impl.HelloServiceImpl.printHello(..))")
public void afterThrowing(){
System.out.println("afterThrowing------");
}
}
或者另外一种写法也行,使用@Pointcut注解,
@Aspect
public class HelloAspect {
@Pointcut("execution(* com.example.demo.services.HelloService.printHello(..))")
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------");
}
}
使用@Aspect注解修饰的类,就是切面(当然也可以使用xml的方式,但这种方式已经不流行了,也就不演示了);
注意,使用@Aspect注解,得额外引用一个jar包(http://www.eclipse.org/aspectj/),竟然不是spring的,
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
注解解释:
这里出现了4四个注解,@Before,前置通知,在被代理对象方法前调用;
@After,后置通知,在被代理对象方法后调用;
@AfterReturning,返回通知,在被代理方法正常返回后调用;
@AfterThrowing,异常通知,在被代理对象方法抛出异常后调用;
还有一种别叫特别的,后边单独弄个例子,
@Around,环绕通知,覆盖原来的方法;
注解参数是一个正则表达式,
execution(* com.example.demo.services.impl.HelloServiceImpl.printHello(…)),解释下,
execution:表示执行方法的时候会触发;
" * " :星号表示任意返回类型的方法;
printHello :被代理的方法,这里也可以不用写实现类的方法,之际写接口方法 com.example.demo.services.HelloService.printHello(…);
(…):任意的参数;
当然,这里只是写了最简单的表达式用法,更多用法,还需要慢慢探索。
2.切面配置类
要想这个切面生效,还需要一个配置类,
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example.demo.aop")
public class AopConfig {
@Bean
public HelloAspect getHelloAspct(){
return new HelloAspect();
}
}
@EnableAspectJAutoProxy注解的作用是使spring识别@Aspect注解;
这样,spring才能识别到切面;
到此,就完成了一个简单的切面,重新跑一下测试方法,输出:
我们发现hello方法前后输出了别的切面中定义的内容;
3.Around通知
接下来说下Around通知,
在HelloAspect切面中加上以下内容,
@Around("print()")
public void around(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("around before");
try {
proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("around after");
}
输出,
通过输出可以发现,环绕通知的执行顺序,挺奇怪的,记住就好;
4.给通知传递参数
有时候我们会有这种需求,就是需要把连接点,也就是被代理对象方法的参数,传递给通知,
我们先修改下service方法,加上个入参String str,
@Override
public void printHello(String str) {
System.out.println("hello,我是被aop的方法,printHello"+str);
}
在修改下切面,
@Pointcut("execution(* com.example.demo.services.HelloService.printHello(..))&&args(str)")
public void print(String str){
}
@Before(value = "print(str)", argNames = "str")
public void before(String str){
System.out.println("before------"+str);
}
我们在切点的正则表达式中添加了:&&args(str)
为了简化,咱们就只验证下前置通知好了,看看这个str参数能不能传递到这里,
运行测试方法,发现参数传递过来了:
5.多个aspect
可以写多个切面,针对同一个连接点;
我们新建一个service
public interface MultiService {
void testMulti();
}
@Service
public class MultiServiceImpl implements MultiService {
@Override
public void testMulti() {
System.out.println("test multiAspect");
}
}
新建3个切面Aspect1,Aspect2,Aspect3
@Aspect
public class Aspect1 {
@Pointcut("execution(* com.example.demo.services.MultiService.testMulti(..))")
public void print(){
}
@Before("print()")
public void before(){
System.out.println("before1------");
}
@After("print()")
public void after(){
System.out.println("after1------");
}
@AfterReturning("print()")
public void afterReturning(){
System.out.println("afterReturning1------");
}
@AfterThrowing("print()")
public void afterThrowing(){
System.out.println("afterThrowing1------");
}
}
其他两个切面类似,不贴代码了;
新建切面配置类:
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example.demo.aop")
public class MultiAopConfig {
@Bean
public Aspect1 getAspect1(){
return new Aspect1();
}
@Bean
public Aspect2 getAspect2(){
return new Aspect2();
}
@Bean
public Aspect3 getAspect3(){
return new Aspect3();
}
}
新建测试类,运行,看输出:
注意下3三个切面的执行顺序;以及before和after的执行顺序;
因为我们没有明确指定3个切面顺序,所以默认按照配置类中声明的顺序(123),比如我们改变下顺序为213:
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example.demo.aop")
public class MultiAopConfig {
@Bean
public Aspect2 getAspect2(){
return new Aspect2();
}
@Bean
public Aspect1 getAspect1(){
return new Aspect1();
}
@Bean
public Aspect3 getAspect3(){
return new Aspect3();
}
}
运行输出:
我们也可以使用@Order注解来指定顺序,比如我们分别指定Aspect1,Aspect2,Aspect3的顺序为 312,
@Aspect
@Order(3)//值越小,优先级越高
public class Aspect1 {
@Aspect
@Order(1)
public class Aspect2 {
@Aspect
@Order(2)
public class Aspect3 {
运行测试输出:
以上是spring aop最简单的一个demo,后续会慢慢丰富内容。