AOP面向切面编程也是Spring框架中一大特性,今天我们来简单体会一下。
本次实验是在SpringBoot框架下进行的。
首先介绍一下大体思路:
一、自定义一个注解Action
二、编写连接点,即要进行切面操作的方法。在需要进行切面操作的方法上加上Action注解来标识。
三、编写切面,在切面中我们需要定义切点(拦截注解为Action的方法),定义在切点的前后所进行的切面操作
四、编写配置类,开启AspectJ支持
五、进行测试
先还要在pom.xml中【导入依赖】:
<!-- spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- aspectj支持 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
一、自定义注解Action
spring现在支持用注解来标识需要进行切面操作的方法,所以用注解是更方便的。如何使用,我们来看。
自定义一个注解Annotation,命名为Action,命名是随意的。
package com.example.demo.aop;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//@Target的ElementType.METHOD指明了这个注解Action是一个只能定义在方法上的注解
@Target(ElementType.METHOD)
//指明该注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action {
//定义注解的属性,注:这里不是接口里的方法,表明是一个String类型的name属性
String name();
}
注解和接口有点相似,其实是不一样的,里面name是这个注解的属性,而在接口中我们称之为方法声明。
大家不需要纠结为什么注解Action中要定义一个属性name,这个是无所谓的,不定义也可以。
我们只用明白,这个注解只是定义的一个标记,日后凡是带有这个注解的方法就是要进行切面操作的。
二、编写Service的方法
package com.example.demo.service;
import org.springframework.stereotype.Service;
import com.example.demo.aop.Action;
@Service
public class TestAopService {
@Action(name = "注解式的action")
public void print() {
System.out.println("正在进行TestAopService的业务操作print。。。");
}
//这个方法就不拦截了
public void show() {
System.out.println("正在进行TestAopService的业务操作show。。。");
}
}
print方法是我们需要进行切面操作的,所以我给它加上一个注解@Action
而show方法我们不拦截它,那我们就不带注解
三、编写切面
Action接口和切面的类都在aop包下:
编写切面是关键的一步
package com.example.demo.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import javassist.bytecode.SignatureAttribute.MethodSignature;
//声明这是一个切面类,并将其放到Spring容器中,成为一个Bean
@Aspect
@Component
public class LogAspect {
//需要建立一个切点,切点是没有方法体的
@Pointcut("@annotation(com.example.demo.aop.Action)")
public void annotationPointcut() {};
//然后围绕切点,定义一些操作
@Before("annotationPointcut()")
//参数JoinPoint用来接受连接点方法的各种信息,这里连接点是TestAopService类中的print方法
public void before(JoinPoint joinPoint) {
//用连接点获得方法签名然后获得方法名
String name = joinPoint.getSignature().getName();
System.out.println("连接点" + name + "准备启用了:");
}
}
解释一下:
用@Pointcut注解我们定义了一个切点,里面的参数是拦截规则。
说通俗点,就是凡是带有Action注解的方法,都算我们的切点! 这应该很好理解了
切点方法是不需要写方法体的,因为切点只是一个标记点,写方法体没有意义。
然后下面@Before注解标识的方法:就是在切点方法执行前,先做的一个方法。
说通俗点,凡是带Action注解的方法执行之前,都要先执行一下我定义的before方法!
因为带Action注解的都是切点,而before注解是在切点前做的操作!到这儿应该就很清楚了。
还有一个@After注解是一回事。
最后,我们的切面它也是一个Bean,所以我们需要将其注明为Componnent
四、编写配置类
SpringBoot中可以有多个配置类,我们在com.example.demo.configuration包下加入一个配置类AopConfig:
package com.example.demo.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.example.demo.aop")
@EnableAspectJAutoProxy//开启AspectJ
public class AopConfig {
}
它就一个作用:开启开启Spring对AspectJ支持
@ComponentScan(“com.example.demo.aop”)这句话是为了扫描aop包下的logAspect切面,但实际上在SpringBoot中这句话不要也可以,因为SpringBoot程序启动的时候,就已经会自动扫描主包以下的所有子包了,将所有的Bean都会注册进容器,所以这里写这个扫描有点多此一举了。
五、测试
新建一个Junit测试
package com.example.demo;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.demo.service.TestAopService;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestAop {
@Autowired
TestAopService testAopService;
@Test
public void testAop() {
testAopService.print();
System.out.println("接着调用show方法:");
testAopService.show();
}
}
运行结果:
我们发现,“连接点print准备启用了”这个输出正是before方法中的输出,随后就执行了切点:print方法,证明我们的切面操作实现成功。
再看看后面的show方法,show方法运行之后,只有自己的业务操作,并没有before方法的输出,这是因为show方法不满足切点的条件,它并没有带Action注解,所以它不会执行切点的before方法。