Spring对AOP的支持
Posted 凯哥学堂
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring对AOP的支持相关的知识,希望对你有一定的参考价值。
声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。
Spring对AOP的支持
创建切点来定义切面所织入的连接点是AOP框架的基本功能,Spring和AspectJ之间有大量的协作,而且Spring对AOP的支持在很多方面借鉴了AspectJ。
Spring提供了4种类型的AOP支持:
基于代理的经典Spring AOP
纯pojo切面
使用 @AspectJ 注解驱动的切面
注入式AspectJ切面(适用于Spring各版本)
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理之上,因此,Spring对AOP的支持局限于方法拦截。
Spring的经典AOP模块并不怎么样,虽然曾经的它的确非常棒。但是现在Spring提供了更简洁和干净的面向切面编程方式。引入了简单的声明式AOP和基于注解的AOP之后,Spring经典的AOP看起来就显得非常笨重和过于复杂,所以现在基本都不再使用Spring经典的AOP方式进行面向切面编程了,本文中也不会再介绍经典的Spring AOP。
Spring AOP框架的一些关键知识点:
Spring通知是Java编写的,所以不需要特殊的开发环境就能开发切面,定义通知所应用的切点可以通过注解或xml来进行配置,通常情况下注解比较常用
Spring是在程序运行时通知对象,通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中,所以我们不需要特殊的编译器来织入Spring AOP的切面
因为Spring的AOP基于动态代理,所以Spring只支持方法级别的连接点,不过方法级别的拦截已经可以满足大部分的需求。如果需要方法拦截之外的连接点拦截功能,那么我们就需要使用AspectJ来补充Spring AOP的功能。~~
Spring AOP的基本概念我们了解得差不多了,下面来简单介绍一下如何使用Spring AOP创建切面:
首先配置依赖的jar包,我这里使用的是maven工程,pom.xml配置内容如下:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
Spring配置文件内容如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd ">
<context:annotation-config/>
<context:component-scan base-package="org.zero01"/>
<aop:aspectj-autoproxy/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" p:driverClass="com.mysql.jdbc.Driver" p:jdbcUrl="jdbc:mysql:///school" p:user="root" p:password="your_password" p:maxPoolSize="10" p:minPoolSize="1" /></beans>
先来介绍几个注解的作用,最后我们会使用AOP来编写一个数据库的事务控制:
@Aspect 注解用于定义一个切面类,示例:
package org.zero01.aop;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;
@Aspect // 定义这是一个切面类@Component
public class TransactionAOP {
}
在介绍其他注解之前,先说明一下如何编写切点,在Spring AOP中的切点是使用AspectJ的切点表达式来定义的。最重要的一点就是Spring仅支持AspectJ切点指示器的一个子集,因为Spring是基于代理的,而某些切点表达式是与基于代理的AOP无关的。
下表列出了Spring AOP所支持的AspectJ切点指示器:
AspectJ 指示器 | 描述 |
---|---|
args() | 限制连接点匹配参数为执行类型的执行方法 |
@args() | 限制连接点匹配参数由执行注解标注的执行方法 |
execution() | 用于匹配是连接点的执行方法 |
this() | 限制连接点匹配AOP代理的Bean引用类型为指定类型的Bean |
target() | 限制连接点匹配目标对象为指定类型的类 |
@target() | 限制连接点匹配目标对象被指定的注解标注的类 |
within() | 限制连接点匹配匹配指定的类型 |
@within() | 限制连接点匹配指定注解标注的类型 |
@annotation | 限制匹配带有指定注解的连接点 |
以上Spring所支持的指示器中,只有execution()指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的,所以execution()指示器是我们编写切点时最主要使用的指示器,其他的指示器则只有需要限制匹配的切点时才会使用。 几个常用的 execution 示器表达式介绍: 匹配所有
execution("* *.*(..)")
匹配所有以set开头的方法
execution("* *.set*(..))
匹配com包下所有的方法
execution("* com.david.biz.service.impl.*(..))
匹配com包以及其子包下的所有方法
execution("* com.david..*(..)")
匹配com包以及其子包下 参数类型为String 的方法
execution("* com.david..*(java.lang.String))
为了介绍Spring的切面,我们需要有个主题来定义切面的切点。为此,我们定义一个DAO接口:
package org.zero01.dao;import org.zero01.pojo.Student;import java.util.List;public interface DAO { public int insert(Student student); public int delete(int sid); public List<Student> selectAll(); public int update(Student student);
}
DAO 可以包含数据库的增删查改,我们希望这些方法在被调用时能够触发切点所匹配的通知,这样就可以进行事务的控制了,所以这些方法都可以是切点。 DAO的实现类,StudentDAO代码如下:
package org.zero01.dao;import org.springframework.stereotype.Component;import org.zero01.pojo.Student;import java.util.List;@Component("stuDAO")public class StudentDAO implements DAO{ public int insert(Student student) {
System.out.println("insert 方法执行了"); return 0;
} public int delete(int sid) {
System.out.println("delete 方法执行了"); return 0;
} public List<Student> selectAll() {
System.out.println("selectAll 方法执行了"); return null;
} public int update(Student student) {
System.out.println("update 方法执行了"); return 0;
}
}
Student表格的字段封装类:
package org.zero01.pojo;import org.springframework.stereotype.Component;@Component("stu")public class Student { private int sid; private String sname; private int age; private String sex; private String address; public int getSid() { return sid;
} public void setSid(int sid) { this.sid = sid;
} public String getSname() { return sname;
} public void setSname(String sname) { this.sname = sname;
} public int getAge() { return age;
} public void setAge(int age) { this.age = age;
} public String getSex() { return sex;
} public void setSex(String sex) { this.sex = sex;
} public String getAddress() { return address;
} public void setAddress(String address) { this.address = address;
}
}
@Before 注解让通知方法在目标方法调用之前执行,属于前置通知,示例:
package org.zero01.aop;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;@Aspect@Componentpublic class TransactionAOP { @Before("execution(* org.zero01.dao.DAO.insert(..))")
public void testBefore(){
System.out.println("@Before 我在目标方法调用前执行");
}
}
测试代码:
package org.zero01.test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.zero01.dao.DAO;import org.zero01.pojo.Student;public class Test { public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("app.xml");
DAO dao = (DAO) app.getBean("stuDAO");
Student student = (Student) app.getBean("stu"); // 调用配置了切点的方法
dao.insert(student);
}
}
运行结果:
@Before 我在目标方法调用前执行insert 方法执行了
@After 注解让通知方法在目标方法调用完毕之后或者抛出异常时执行,属于后置通知,示例:
package org.zero01.aop;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;@Aspect@Componentpublic class TransactionAOP { @After("execution(* org.zero01.dao.DAO.insert(..))")
public void testAfter() {
System.out.println("@After 我在目标方法调用完毕后执行");
}
}
测试代码和之前一样,略。
运行结果:
insert 方法执行了@After 我在目标方法调用完毕后执行
@AfterReturning 注解让通知方法在目标方法调用完毕之后执行,属于返回通知,它与 @After 注解主要的区别在于当目标方法抛出异常时,使用@AfterReturning配置的方法是不会执行的,而@After 配置的方法会执行,示例:
package org.zero01.aop;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;@Aspect@Componentpublic class TransactionAOP { @AfterReturning("execution(* org.zero01.dao.DAO.insert(..))")
public void testAfterReturning() {
System.out.println("@AfterReturning 我在目标方法调用完毕后执行");
}
}
测试代码和之前一样,略。
运行结果:
insert 方法执行了@AfterReturning 我在目标方法调用完毕后执行
@AfterThrowing 注解让通知方法在目标方法抛出异常时执行,属于异常通知,示例:
package org.zero01.aop;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;@Aspect@Componentpublic class TransactionAOP { @AfterThrowing("execution(* org.zero01.dao.DAO.insert(..))")
public void testAfterThrowing() {
System.out.println("@AfterThrowing 我在目标方法调用完毕后执行");
}
}
StudentDAO的insert方法修改如下:
public int insert(Student student) {
System.out.println("insert 方法执行了"); throw new NullPointerException();
}
测试代码和之前一样,略。
运行结果:
insert 方法执行了异常打印...略...
@AfterThrowing 我在目标方法抛出异常时执行
所以 @AfterReturning 和 @AfterThrowing 合体后就是 @After。
@Around 注解让通知方法在目标方法调用前后都执行,属于环绕通知,示例:
package org.zero01.aop;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;@Aspect@Componentpublic class TransactionAOP { @Around("execution(* org.zero01.dao.DAO.insert(..))")
public int testAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("@Around 我在目标方法调用前执行"); // 把调用传递到目标方法上
proceedingJoinPoint.proceed();
System.out.println("@Around 我在目标方法调用后执行"); return 0;
}
}
如上代码中,可以看到 @Around 注解的使用方式和之前所介绍到的注解就有些不同了,首先该方法的返回值要和目标方法的返回值一致,然后就是需要接收 ProceedingJoinPoint 类型的参数,Spring会在织入切点时自动将这个参数传递进来。除此之外还需要通过调用该参数对象的 proceed 方法将调用传递到目标方法上,这一点类似于Servlet技术中Filter过滤器的doFilter方法。
这个 @Around 注解之所以能够做到环绕通知,就是因为可以在 proceed 方法的前后写上代码,这样就把 proceed 方法围起来了形成一个环绕通知,就如上的面的示例。
测试代码和之前一样,略。
运行结果:
@Around 我在目标方法调用前执行insert 方法执行了@Around 我在目标方法调用后执行
要注意的是当目标方法抛出异常时,proceed 方法下面的代码是不会被执行的,例如:
StudentDAO的insert方法修改如下:
public int insert(Student student) {
System.out.println("insert 方法执行了"); throw new NullPointerException();
}
切面代码以及测试代码和之前一样,略。 运行结果: @Around 我在目标方法调用前后都执行 insert 方法执行了 异常打印...略... 之前我们提到过,Spring的AOP是基于动态代理的,那么我们就来看看拿出来的是不是代理对象,测试代码如下:
package org.zero01.test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.zero01.dao.DAO;public class Test { public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("app.xml");
DAO dao = (DAO) app.getBean("stuDAO");
System.out.println(dao.getClass().getName());
}
}
运行结果:
com.sun.proxy.$Proxy7
从运行结果中可以看到,打印出来的是典型的代理对象名称。但是这只是其中一种情况,因为StudentDAO实现了DAO接口,所以这时候拿出来的是实现了该接口的代理对象。现在我把StudentDAO的实现语句去掉之后,看看拿出来的是否还是代理对象。测试代码如下:
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("app.xml");
StudentDAO dao = (StudentDAO) app.getBean("stuDAO");
System.out.println(dao.getClass().getName());
}
运行结果:
org.zero01.dao.StudentDAO
如上,打印的结果并不是代理对象,这是因为StudentDAO没有实现的接口,这时产生的代理类是继承于StudentDAO的,所以拿出StudentDAO对象的时候并不是一个代理对象。
@Pointcut 注解能够在一个切面类里定义可复用的切点,例如以上我们代码中写的指示器表达式基本都是一样的,这时候我们就可以使用 @Pointcut 注解来定义一个可复用的切点,示例:
package org.zero01.aop;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Aspect@Componentpublic class TransactionAOP { @Pointcut("execution(* org.zero01.dao.StudentDAO.insert(..))")
public void dao() {
} @Before("dao()")
public void testBefore(){
System.out.println("@Before 我在目标方法调用前执行");
} @After("dao()")
public void testAfter() {
System.out.println("@After 我在目标方法调用完毕后执行");
} @AfterReturning("dao()")
public void testAfterReturning() {
System.out.println("@AfterReturning 我在目标方法调用完毕后执行");
} @AfterThrowing("dao()")
public void testAfterThrowing() {
System.out.println("@AfterThrowing 我在目标方法调用完毕后执行");
} @Around("dao()")
public int testBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("@Around 我在目标方法调用前执行"); // 把调用传递到目标方法上
proceedingJoinPoint.proceed();
System.out.println("@Around 我在目标方法调用后执行"); return 0;
}
}
测试代码:
package org.zero01.test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.zero01.dao.DAO;import org.zero01.pojo.Student;public class Test { public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("app.xml");
DAO dao = (DAO) app.getBean("stuDAO");
Student student = (Student) app.getBean("stu");
dao.insert(student);
}
}
运行结果:
@Around 我在目标方法调用前执行@Before 我在目标方法调用前执行insert 方法执行了@Around 我在目标方法调用后执行@After 我在目标方法调用完毕后执行@AfterReturning 我在目标方法调用完毕后执行
从这个打印结果中,我们还可以看到这些注解的优先级。 把以上所介绍到的注解总结成表如下:
注解 | 作用 |
---|---|
@Aspect | 用于定义一个切面类 |
@Before | 让通知方法在目标方法调用之前执行 |
@After | 让通知方法在目标方法调用完毕之后或者抛出异常时执行 |
@AfterReturning | 让通知方法在目标方法调用完毕之后执行 |
@AfterThrowing | 让通知方法在目标方法抛出异常时执行 |
@Around | 让通知方法在目标方法调用前后都执行 |
以上是关于Spring对AOP的支持的主要内容,如果未能解决你的问题,请参考以下文章