耐心读完相信你会有所收获
目录
什么是AOP
AOP直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
将多个开发模块中某段重复的方法,抽出来。这
初步认识Spring AOP
Spring 中的 AOP 是通过动态代理实现的。不同的 AOP 框架支持的连接点也有所区别,例如,AspectJ 和 JBoss,除了支持方法切点,它们还支持字段和构造器的连接点。而 Spring AOP 不能拦截对对象字段的修改,也不支持构造器连接点,我们无法在 Bean 创建时应用通知。
package com.dome.test1;
public interface AB {
String eat();
}
A实现AB接口
package com.dome.test1;
@Component
public class A implements AB {
@Override
public String aeat() {
System.out.println("a吃饭");
return "吃饭";
}
}
B实现AB接口
package com.dome.test1;
@Component
public class B implements AB {
@Override
public String beat() {
System.out.println("b不吃饭");
return "不吃饭";
}
}
配置文件
package com.demo;
@Configuration
@ComponentScan(basePackageClasses = {com.demo.test1.eat.class})
public class AppConfig {
}
测试类
package com.demo;
public class AppTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
A a = context.getBean("a",A.class);
B b = (b) context.getBean("b");
a.eat();
b.eat();
}
}
运行结果
a吃饭
b不吃饭
如果我们想在a,b输出结果之前加入一个其他的方法是不是a,b两个方法都需要改呢。
这是就用到了Spring AOP很见简单的你就可以完成了。
package com.demo.test1;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class EatAspectJ {
@Before("execution(* com.demo.test1.AB.eat(..))")
public void ceshi(){
System.out.println("AB喜欢的东西");
}
}
这个类,注解 @Component 表明它是一个Spring Bean 被装配,通过 @Aspect 表示它是一个切面。
类中方法 ceshi 前的@Before 注解,表示他将在方法执行之前执行。
参数 ("exection(* com.demo.test1.AB.eat(..))")声明了切点位置,表明在该位置切入切点。
package com.demo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackageClasses = {com.demo.test1.AB.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
你能看到运行结果是:AB喜欢的东西
a吃饭
AB喜欢的东西
b不吃饭
配置文件中的@EnableAspectJAutoProxy注解,是用于启动AOP功能的,参数proxyTargetClass的值设为True;如果我们不写参数,默认为 false。这个时候运行程序,程序抛出异常。
这是由于proxyTargetClass参数决定了代理的机制,当这个参数为false时,通过jdk的基于接口的方式进行织入,这时候代理生成的是一个接口对象,将这个接口对象强制转换为实现该接口的一个类,自然就抛出了上述类型转换异常。
当这个参数为true时,会使用cglib的动态代理方式这种方式的缺点是拓展类的方法被final修饰时,无法进行织入。
测试一下,把final加入aeat()方法中
A实现AB接口
package com.dome.test1;
@Component
public class A implements AB {
@Override
public final String aeat() {
System.out.println("a吃饭");
return "吃饭";
}
}
运行结果:你可以看到切面组织唯有生效
通过注解配置Spring AOP
AspectJ指示器 | 描述 |
arg() | 限制连接点匹配参数为指定类型的执行方法 |
@args() | 限制连接点匹配参数由指定注解标注的执行方法 |
execution() | 用于匹配是连接点的执行方法 |
this() | 限制连接点匹配AOP代理的Bean引用为指定类型的类 |
target() | 限制连接点匹配目标对象为指定类型的类 |
@target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解 |
within() | 限制连接点匹配指定的类型 |
@within() | 限制连接点匹配指定注解所标注的类型(当前使用Spring AOP时,方法定义在有指定的注解所标注的类里) |
@annotation | 限制连接点匹配带有指定注解连接点 |
其他都是限制链接只有execution指示器是唯一的执行匹配,这说明execution在编写切点时非常的重要。
execution(* com.demo.test1.AB.eat(...))
execution:在方法执行时触发
*:返回任意类型
com.demo.test1.AB :方法所属的类型
eat:此方法
... :使用任意参数
多个匹配之间我们可以使用链接符 &&、||、!来表示 “且”、“或”、“非”的关系。
但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。
package com.demo.test1;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class EatAspectJ {
@Before("execution(* com.demo.test1.AB.eat(..))
&& within(* com.demo.*) && bean(girl)")
public void ceshi(){
System.out.println("AB喜欢的东西");
}
}
此时运行时结果: a吃饭
AB喜欢的东西
b不吃饭
通过注解 声明5种通知类型
注解 | 通知 |
@Beforre | 通知方法会在目标方法调用之前执行 |
@After | 通知方法会在目标方法返回或异常后调用 |
@AfterReturning | 通知方法会在目标方法返回调用 |
@AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around | 通知方法会目标方法封装起来 |
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class EatAspectJ {
@Before("execution(* com.demo.test1.AB.eat(..))")
public void hehe() {
System.out.println("before ...");
}
@After("execution(* com.demo.test1.AB.eat(..))")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("execution(* com.demo.test1.AB.eat(..))")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("execution(* com.demo.test1.AB.eat(..))")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
测试类
package com.demo;
public class AppTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
A a = context.getBean("a",A.class);
a.eat();
}
}
运行结果
Around aaa...
before...
a吃饭
Around bbb...
After ...
AfterReturning ...
@Around修饰的环绕通知类型,将整个目标方法封装起来了,在使用时,我们传入了ProceedingJoinPoint 类型的参数,这个对象是必须要有的,并且需要调用ProceedingJoinPoint 的 proceed() 方法。 如果没有将不会环绕,
执行结果为
before...
a吃饭
Around aaa...
Around bbb...
After ...
AfterReturning ...
如果不调用对象proceed方法,表示原目标方法被堵塞了
多个通知使用相同的切点表达式,可以使用 @Pointcut 注解声明切点表达式
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class EatAspectJ {
@Pointcut("execution(* com.demo.test1.AB.eat(..))")
public void test(){}
@Before("test()")
public void hehe() {
System.out.println("before ...");
}
@After("test()")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("test())")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("test()")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
@Pointcut("execution(* com.demo.test1.AB.eat(..))")
public void test(){}
这个方法相当于作为一个表示供通知使用。
在说一下 *的用法,你如果不关心返回值是什么用* ,后边的 ... 表示任意参数。
如果需要传参数和需要返回值的时候,需要自己编写传参类型和返回值类型
编写返回类型和传入参数与未编写进行对比
package com.sharpcj.aopdemo.test1;
@Aspect
@Component
public class EatAspectJ {
@Pointcut("execution(String com.demo.test1.AB.eat(String))")
public void test(String a){}
@Before("test(a)")
public void hehe() {
System.out.println("before ...");
}
@Around("test(a)")
public void xxx(ProceedingJoinPoint pj,String a) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Pointcut("execution(* com.demo.test1.AB.eat(..))")
public void test(){}
@Before("test()")
public void hehe() {
System.out.println("before ...");
}
@After("test()")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("test())")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("test()")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
参考来源Spring AOP——Spring 中面向切面编程 - SharpCJ - 博客园 (cnblogs.com)参考来源