AOP本质:将切面类和目标类交给Spring去进行动态代理(后置处理器)(Around注解)。
Spring容器只要有被切的目标类,则IOC容器中存放的就是对应类的代理对象,最后通过代理对象调用方法实现面向切面编程。
如果目标类实现了接口,当通过类型取得Bean的时候,必须传入接口的类型,不能是本类的类型,才能获得代理对象。
如果目标类没有实现接口,则Spring的cglib会为它创建代理对象。
开启注解的AOP功能:<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
maven工程还需要通过pom.xml增加以下依赖导入jar包:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
1、AOP注解注入的术语
目标类:交给后置处理器(本质应该也是动态代理)代理的类。
切面类:委托类。
通知方法:切面类中的各个方法。
连接点:委托类中每个通知方法的每个位置。
切入点:连接点中感兴趣的位置。
2、连接点的位置
单个方法的连接点位置,见下面的灵魂制图:
3、AOP普通注释
@Aspect:在该Bean已经加入IOC容器的前提下,指定其为切面类。
以下注解的执行顺序按下面解释的顺序执行,After和AfterThrowing、AfterReturning顺序是反的。
@Before(“execution(访问修饰符 返回类型 全类名.方法名(参数类型))”):代理方法执行前执行的方法。
@After(“execution(访问修饰符 返回类型 全类名.方法名(参数类型))”):代理方法执行后执行的方法。
@AfterThrowing(“execution(访问修饰符 返回类型 全类名.方法名(参数类型))”):代理方法执行抛出异常后执行的方法。
@AfterReturning(“execution(访问修饰符 返回类型 全类名.方法名(参数类型))”):代理方法正常执行后执行的方法。括号里的execution用于指定切入点的位置。
抽取可重用的切入点表达式:@Pointcut(括号里放需要重用的切入点表达式)。
可以通过JoinPoint可以获得方法参数和方法名等等。通过指定方法位置注解的returning属性,可以获得方法的返回值。
目标类1代码:
public interface BookDao {
public void buy(String bookName);
}
@Repository
public class BookDaoImpl implements BookDao {
public void buy(String bookName) {
System.out.println("BookDao买了一本" + bookName + "书");
}
}
目标类2代码:
@Repository
public class BookDao2 {
public String buy2(String bookName) {
System.out.println("BookDao2买了一本" + bookName + "书");
return bookName;
}
}
切面类代码:
@Aspect
@Component
public class LogUtils {
@Pointcut("execution(public void cj.dao.BookDaoImpl.buy(String)) || execution(public String cj.dao.BookDao2.buy2(String))")
public void formal() {
}
@Before("formal()")
public void before(JoinPoint joinPoint) { //该方法是Spring利用反射创建的, 需要正确指定参数
Object[] objects = joinPoint.getArgs();
System.out.println("Before方法" + Arrays.asList(objects));
}
@AfterReturning(value = "formal()", returning = "result")
public void afterReturning(Object result) {
System.out.println("AfterReturning方法, 返回为" + result);
}
@AfterThrowing(value = "formal()", throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("异常" + e + "出现了, AfterThrowing方法");
}
@After("formal()")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法执行结束, After方法");
}
}
测试代码:
public class Exp5 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean5.xml");
BookDao o = (BookDao) applicationContext.getBean("bookDaoImpl");
System.out.println(o.getClass());
o.buy("中华上下五年上册");
BookDao2 bookDao2 = (BookDao2) applicationContext.getBean("bookDao2");
System.out.println(bookDao2.getClass());
bookDao2.buy2("中华上下五年下册");
}
}
运行结果:
4、Spring中最牛逼的环绕通知
@Around:就是动态代理。
目标类代码:
@Repository
public class BookDao3 {
@Override
public String toString() {
return "BookDao3{}";
}
public String buy3(String bookName) {
System.out.println("BookDao2买了一本" + bookName + "书");
return bookName;
}
}
切面类代码:
public class LogUtils {
@Around("execution(public String cj.dao.BookDao3.buy3(String))")
public Object NB(ProceedingJoinPoint pjp) {
Object result = null;
Object[] args = pjp.getArgs();
try {
System.out.println("之前");
result = pjp.proceed(args);//相当于通过这个方法代理了目标类的方法
System.out.println("之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常");
}
System.out.println("结束");
return result;
}
}
测试代码:
public class Exp5 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean5.xml");
BookDao3 bookDao3 = (BookDao3) applicationContext.getBean("bookDao3"); //取得的是代理对象
Object result = bookDao3.buy3("美丽心灵");
System.out.println(result);
}
}
测试结果:
最强环绕通知注解成功,而且执行顺序也是对的,没有像之前的那四个通知那样After和AfterReturning顺序是反的。
还可以通过@Order注解指定多切面的执行顺序,value越小越先执行。