Aspectj是什么?
是一个基于java语言的AOP框架。搭配的有自己创建的编译器,支持Java语法,也创建一套自有的语法,通过操作字节码产生代理对象,专门用于AOP。
Spring2.0以后新增了切点表达式支持,
AspectJ1.5中新增了对注解的支持,允许直接在Bean类中定义切面。
新版本的Spring框架建议我们都使用AspectJ方式来开发AOP,并提供了非常灵活且强大的切点表达式 ;
当然无论使用Spring自己的AOP还是AspectJ相关的概念都是相同的;
但是无论是哪种方式,关键点都在于切点和通知
注解配置
导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
通知类型
首先需要创建一个普通类作为通知类,@AspectJ用于标注其属于通知类,见下面案例:
@Before 前置通知 在原始方法执行前执行
@AfterReturning 后置通知 在原始方法执行后执行
@Around 环绕通知 彻底拦截原始方法的执行,执行前后都可以增加逻辑,也可以不执行原始方法
@AfterThrowing抛出通知,执行原始方法出现异常时执行
@After 最终final通知,不管是否异常,原始方法调用后都会执行
@DeclareParents 引介通知,相当于IntroductionInterceptor (了解即可)
定义切点
通过execution函数来定义切点
语法:execution(权限修饰符 返回类型 方法名 (参数列表) 异常)
注意:如果某一项不限制条件,则使用*表示
如果 权限修饰符 和 返回类型 都是*,只写一个即可。
如果全都不限如下:
execution(* *(…))
切点表达式示例
匹配所有类public方法:execution(public * *(..))
第一个*表示返回值 …表示任意个任意类型的参数
匹配指定包下所有类所有方法: execution(* cn.xxx.dao.*.*(..))
第一个想*表示忽略权限和返回值类型
匹配指定包下所有类所有方法:execution(* cn.xxx.dao..*(..))
包含子包
匹配指定类所有方法: execution(* cn.xxx.service.UserService.*(..))
匹配实现特定接口所有类方法 :execution(* cn.xxx.dao.GenericDAO+.*(..))
匹配所有save开头的方法: execution(* save*(..))
匹配某个指定的方法: execution(* com.yh.dao.StudentService.save(..))
具体使用
准备工作
pom依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>compile</scope>
</dependency>
<!-- Maven会自动下载所有Spring核心容器和aop的依赖-->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
</dependencies>
接口:
public interface PersonDao {
void haha();
void select();
void update();
void delete();
}
实现类:
public class PersonDaoImpl implements PersonDao {
public void haha() {
System.out.println("哈哈哈哈哈哈哈");
}
public void select() {
System.out.println("查询!");
}
public void update() {
System.out.println("更新!");
}
public void delete() {
System.out.println("删除!");
}
}
切面类:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
//所有通知注解,都必须同时指定切点表达式 用于匹配方法
//public void 方法名称(参数列表,填的是参数类型) * 表示通配符
//(..)表示任意个数的任意类型参数都可以
@Before("execution(* *(..))")
public void before(){
System.out.println("前置通知执行了");
}
}
配置applicationContext.xml
<!--目标类-->
<bean id="personDao" class="cx.dao.PersonDaoImpl"/>
<!--配置切面-->
<bean id="myAspect" class="cx.dao.MyAspect"/>
<!--启用AspectJ-->
<aop:aspectj-autoproxy/>
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class test1 {
@Autowired
private PersonDao personDao;
@Test
public void test1(){
personDao.haha();
personDao.delete();
personDao.select();
personDao.update();
}
}
余下的后置通知:
@AfterReturning("execution(* cx.dao.*.*t(..))")
public void afterReturning() {
System.out.println("dao包下的所有最后一个带t的方法执行后置通知");
}
环绕通知:
@Around("execution(* *.e(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("环绕前");
try {
Object proceed = proceedingJoinPoint.proceed();
System.out.println("环绕后");
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
环绕通知与其他通知最大的区别在于环绕通知可以控制是否调用原始方法
注意:参数类型必须为ProceedingJoinPoint,否则 无法执行原始方法,
异常通知:
@AfterThrowing(value = "execution(* *haha(..))", throwing = "e")
public void throwing(Exception e) {
System.out.println("异常" + e);
}
模拟异常:
public void haha() {
int i = 1 / 0;
System.out.println("哈哈哈哈哈哈哈");
}
当方法中出现异常时才会执行该通知,若需要获取异常信息,可在注解中添加throwing指定参数名称
我们可以使用环绕+异常通知来处理数据库事务,在环绕中开启事务以及提交事务,异常通知中回滚事务,当然Spring已经对事务进行了封装不需要自己写
上面出现异常你会发现后置通知没有执行,因为后置通知是只有正确返回时才会执行。
最终通知:
//下面是不论何种权限和返回值,以haha结束的方法 任意参数列表
@After("execution(* *haha(..))")
public void after() {
System.out.println("最终通知");
}
上面代码可以知道,虽然出现异常停止执行,但是最终通知依然执行了。
逻辑操运算符的表达式
在表达式中可以使用户逻辑操运算符,与&& 或|| 非!
示例:
/*
execution(* cn.xxx.service.UserDao.insert(..))||execution(* cn.xxx.service.UserDao.delete(..))
execution(* cn.xxx.service.UserDao.*nsert(..))&&execution(* cn.xxx.service.UserDao.inser*(..))
!execution(* cn.xxx.service.UserDao.insert(..))
*/
切点命名
假设有多个通知应用在同一个切点上时,我们需要重复编写execution表达式,且后续要修改切点时则多个通知都需要修改,维护起来非常麻烦,可以通过给切点指定名称从而完成对切点的重复使用和统一操作,以提高开发维护效率。
//定义命名切点 方法名称即切点名称
@Pointcut(value = "execution(* *delete(..))")
public void pointDemo(){
}
@Pointcut(value = "execution(* *ha(..))")
public void pointDemo2(){
}
多个通知应用到同一个切点:
@After(value = "pointDemo()")
public void after() {
System.out.println("最终通知");
}
@Around(value = "pointDemo()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("环绕前");
try {
Object proceed = proceedingJoinPoint.proceed();
System.out.println("环绕后");
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
一个通知应用多个切点:
@Before(value = "pointDemo()||pointDemo2()")
public void before() {
System.out.println("前置通知执行了");
}
xml配置
XML配置所需的jar 以及各个对象之间的依赖关系以及表达式的写法都是一样的,仅仅是换种方式来写而已;
通知类:
public class MyAdvice2 {
public void beforeAdvice() {
System.out.println("我是前置通知");
}
public void afterReturningAdvice() {
System.out.println("我是后置通知");
}
public void afterAdvice() {
System.out.println("我是最终通知");
}
public void throwingAdvice(Exception e) {
System.out.println("我是异常通知,因为" + e);
}
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("环绕前");
try {
Object proceed = proceedingJoinPoint.proceed();
System.out.println("环绕后");
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
配置文件:applicationContext.xml
<!--目标类-->
<bean id="personDao" class="cx.dao.PersonDaoImpl"/>
<!--通知-->
<bean id="myAspect2" class="cx.dao.MyAdvice2"/>
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* *t(..))"/>
<aop:aspect ref="myAspect2">
<aop:after method="afterAdvice" pointcut-ref="pointcut"/>
<aop:before method="beforeAdvice" pointcut-ref="pointcut"/>
<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturningAdvice" pointcut-ref="pointcut"/>
<aop:after-throwing method="throwingAdvice" pointcut-ref="pointcut" throwing="e"/>
</aop:aspect>
</aop:config>
无论是XML还是注解都不需要手动指定代理,以及目标对象,Aspectj会从切点中获取目标对象信息并自动创建代理。