Spring使用xml创建切面和使用AspectJ创建切面有所不同的。下面使用AspectJ来创建切面。
一、先举个栗子
所谓aop就是面向切面编程,使其他类共享相同的行为,比如日常开发中使用日志模块,总不能每次使用都new一个出来,这时候就要用到aop,我们先管他三七二十一,定义一个切面类,类中写方法,然后把方法直接搞到其他方法内部。先举个栗子,比如我们经常逛b站鬼畜区,什么Van,香蕉君啥的,哲学你们都懂得。那么,栗子来了,Van口头禅FQ,但是Banana君等等也想要说FQ,但是Banana没有自己不会说FQ,他只会跳舞和摔跤,Banana想要在跳舞和摔跤时候也说FQ,没法只能由Van来说FQ。
准备工作,先把pom写好
4.0.0
com.yoyiyi
TestAOP
1.0-SNAPSHOT
jar
TestAOP
http://maven.apache.org
UTF-8
1.8
4.1.5.RELEASE
org.springframework
spring-context
${spring-framework.version}
org.springframework
spring-aop
${spring-framework.version}
org.aspectj
aspectjrt
1.8.6
org.aspectj
aspectjweaver
1.8.5
org.springframework
spring-test
${spring-framework.version}
junit
junit
4.11
commons-io
commons-io
2.3
javax.annotation
jsr250-api
1.0
org.apache.maven.plugins
maven-compiler-plugin
2.3.2
${java.version}
二、定义切面
既然Banana不会说FQ,Van会,我们先把Van搞过来,就是定义一个切面。
@Aspect //表明是一个切面
@Component //表明是一个组件
public class Van {
//定义公共切点,这样不用以后每一次都写一遍 切入到Banana摔跤方法
@Pointcut("execution(* com.yoyiyi.java.Banana.摔跤())")
public void poincut() {
}
//前置通知 在Banan摔跤之前说FQ
@Before("poincut()")
public void 前置sayFQ() {
System.out.println("前置FQ");
}
//后置通知 在Banana摔跤之后说FQ
@After("poincut()")
public void 后置sayFQ() {
System.out.println("后置FQ");
}
//异常通知 Banana 摔跤发生异常说FQ 比如没体力
@AfterThrowing("poincut()")
public void 异常sayFQ() {
System.out.println("异常FQ");
}
//返回后通知 Van想知道Banana跳舞之后在干啥 Van不知道,所以Object
@AfterReturning(returning = "rvt",
pointcut = "execution(* com.yoyiyi.java.Banana.跳舞())")
public void 返回通知sayFQ(Object rvt) {
System.out.println("返回通知FQ,发现蕉说" + rvt.toString());
}
//环绕通知 Van在Banana摔跤之前之后都说FQ
@Around("poincut()")
public void 环绕sayFQ(ProceedingJoinPoint joinPoint) {
try {
System.out.println("前面FQ");
joinPoint.proceed();//执行方法
System.out.println("后面FQ");
} catch (Throwable throwable) {
System.out.println("异常FQ");
throwable.printStackTrace();
}
}
二、目标对象
这个目标对象就是Banana
@Component //Banana组件
public class Banana {
public void 摔跤() {
//异常通知
//int i = 1;
//i=i/0;
System.out.println("摔跤");
}
public String 跳舞() {
return "我还要跳舞";
}
}
三、装配
既然Van和Banana都有了,总有个地方要把他们弄到一起吧,b站通常在鬼畜区就可以了,也就是装配到Spring的bean中。
@Configuration //相当于一个xml配置文件
@EnableAspectJAutoProxy //表示开启AOP代理自动配置
@ComponentScan(basePackages = "com.yoyiyi.java") //扫描com.yoyiyi.java 注解
public class GuiChuConfig { //配置
}
四、测试
我们写一个测试类看看效果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = GuiChuConfig.class) //找到鬼畜区配置类
public class TestBanana {
//测试一个通知时候 需要把Van中其他通知注释掉
@Autowired 自动装配
Banana mBanana;
@Test
public void 前置() throws Exception {
mBanana.摔跤();
//控制台:
//前置FQ
//摔跤
}
@Test
public void 后置() throws Exception {
mBanana.摔跤();
//控制台:
//后置FQ
//摔跤
}
@Test
public void 环绕() throws Exception {
mBanana.摔跤();
//控制台:
//前面FQ
//摔跤
//后面FQ
}
@Test
public void 返回() throws Exception {
mBanana.跳舞();
//控制台:
//返回通知FQ,发现蕉说我还要跳舞
}
@Test
public void 异常() throws Exception {
mBanana.摔跤();
//控制台:
//异常FQ
//摔跤
}
五、目标方法带有参数
举个栗子:
Banana要和Bili摔跤,所以要在摔跤方法中传入Bili大名,Van说FQ并且要知道Bili和Banana摔跤多少次。
在Banana中添加一个带有参数摔跤方法
public void 摔跤(String name) {
System.out.println("摔跤");
}
在Van中添加一个前置通知
@Before("execution(* com.yoyiyi.java.Banana.摔跤(String)) && args(name))")
public void 带统计前置sayFQ(String name) {
System.out.println("前置FQ");
if ("bili".equals(name)) {
System.out.println("统计" + 1000);
}
}
在测试类中添加测试方法
@Test
public void 统计() throws Exception {
mBanana.摔跤("bili");
//控制台:
//前置FQ
// 统计摔跤次数1000
//摔跤
}
六、自定义注解
在现实开发中日志文件带有记录基本增删改的功能,但是表信息,权限等难记录就要用到自定义注解
举个栗子:
鬼畜视频是up制作,Van说FQ时候想知道这是哪个up主搞的视频,他想知道就是up主名字和年龄。
定义一个Up主注解类
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Up {
/**
* 姓名
*/
String upName() default "";
/**
* 名字
*/
int upAge() default 0;
}
在Banana里面摔跤方法添加注解
@Up(upAge = 15, upName = "van")
public void 摔跤() {
System.out.println("摔跤");
}
在Van里面添加
@Before("poincut()")
public void 前置带有up主名字和年龄sayFQ(JoinPoint joinPoint) {
System.out.println("前置FQ");
try {
int upAge = getUpAge(joinPoint);
String upName = getUpName(joinPoint);
System.out.println("up姓名:" + upName);
System.out.println("up年龄:" + upAge);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取Up名字
private String getUpName(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String upName = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] types = method.getParameterTypes();
if (types.length == arguments.length) {
upName = method.getAnnotation(Up.class).upName();
break;
}
}
}
return upName;
}
//获取Up年龄
private int getUpAge(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
int upAge = 0;
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] types = method.getParameterTypes();
if (types.length == arguments.length) {
upAge = method.getAnnotation(Up.class).upAge();
break;
}
}
}
return upAge;
}
在测试类添加
@Test
public void 前置带有up主名字和年龄() throws Exception {
mBanana.摔跤();
//控制台:
//前置FQ
//up姓名:van
//up年龄:15
//摔跤
}
至此,Spring AOP使用注解创建切面骚操作差不过讲完了。