前面介绍过AOP面向切面编程是一种编程思想,是对OOP面向对象编程的一种补充。对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。而AspectJ也实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。后面使用AOP编程都是在Spring环境下使用AspectJ来进行的。
AspectJ的五种常用通知类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知
其中最终通知是指,无论程序执行是否正常,该通知都会执行。类似于try...catch中的finally代码块。
AspectJ的切入点表达式
AspectJ定义了专门的表达式用于指定切入点。表达式的原型如下:
execution([modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
[declaring-type-pattern] 全限定性类名
name-pattern(param-pattern) 方法名(参数名)
[throws-pattern] 抛出异常类型
)
切入点表达式要匹配的对象就是目标方法的方法名。表达式中加[]的部分表示可省略的部分,红色部分表示不可省略的部分,各部分之间用空格分开。在其中可以使用一下符号:
举几个例子:
execution(public * *(..)) 指定切入点为:任意公共方法
execution(* *.service.*.*(..)) 指定只有一级包下的service子包下所有类(接口)中所有方法为切入点
execution(* *..service.*.*(..)) 指定所有包下的service子包下所有类(接口)中所有方法为切入点
execution(* *.SomeService.*(..)) 指定只有一级包下的SomeService类(接口)中所有方法为切入点
execution(* *..SomeService.*(..)) 指定所有包下的SomeService类(接口)中所有方法为切入点
AspectJ的开发环境
引入AOP联盟的jar包,这是AOP的规范
引入AspectJ的jar包,这是对上面AOP规范的实现
引入整合Spring和AspectJ的jar包
引入Spring的AOP jar包,因为上面Spring和AspectJ的整合jar包用到了Spring的AOP jar包
注意:这里只列举了需要额外引入的jar包,还需要Spring的四个基础包和日志包
基于注解的实现:
前置通知:
首先定义好要增强的接口和实现类
package com.hnu.service;
public interface SomeService {
void doFirst();
String doSecond();
void doThird();
}
package com.hnu.service;
public class SomeServiceImpl implements SomeService {
@Override
public void doFirst() {
System.out.println("执行doFirst()方法");
}
@Override
public String doSecond() {
System.out.println("执行doSecond()方法");
return "abcde";
}
@Override
public void doThird() {
System.out.println("执行doThird()方法");
}
}
编写切面类(是一个POJO类)
package com.hnu.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect //表示当前类是切面
public class MyAspect {
//@Before表示这是一个后置通知 execution表示匹配的连接点
@Before("execution(* *..SomeService.doFirst(..))")
public void myBefore(){
System.out.println("执行前置通知方法");
}
@Before("execution(* *..SomeService.doFirst(..))")
public void myBefore(JoinPoint jp){ //JoinPoint表示连接点
System.out.println("执行前置通知方法 jp = " + jp);
}
}
配置xml文件
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册切面 -->
<bean id="myAspect" class="com.hnu.service.MyAspect"/>
<!-- 注册目标对象 -->
<bean id="someService" class="com.hnu.service.SomeServiceImpl"/>
<!-- 注册AspectJ的自动代理 ,使用到了AOP约束-->
<aop:aspectj-autoproxy/>
</beans>
测试类
package com.hnu.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
//创建容器对象,加载Spring配置文件
String resource = "com/hnu/service/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
SomeService service = (SomeService) ac.getBean("someService");
service.doFirst();
System.out.println("--------------------------");
service.doSecond();
System.out.println("--------------------------");
service.doThird();
}
}
运行结果:
后置通知,环绕通知,异常通知的使用和前置通知相同,唯一需要改变的就是切面类,这里就只列出切面类的写法
package com.hnu.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect //表示当前类是切面
public class MyAspect {
@Before("execution(* *..SomeService.doFirst(..))")
public void myBefore(){
System.out.println("执行前置通知方法");
}
@Before("execution(* *..SomeService.doFirst(..))")
public void myBefore(JoinPoint jp){
System.out.println("执行前置通知方法 jp = " + jp);
}
@AfterReturning("execution(* *..SomeService.doSecond(..))")
public void myAfterReturning(){
System.out.println("执行后置通知方法");
}
//注意这里虽然能获取方法返回值,但是不能修改
@AfterReturning(value="execution(* *..SomeService.doSecond(..))",returning="result")
public void myAfterReturning(Object result){
System.out.println("执行后置通知方法 result= " + result);
}
@Around("execution(* *..SomeService.doSecond(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("执行环绕通知方法,目标方法执行之前");
Object result = pjp.proceed();
System.out.println("执行环绕通知方法,目标方法执行之后");
if(result != null){ //可以修改目标方法的返回结果
result = ((String)result).toUpperCase();
}
return result;
}
@AfterThrowing("execution(* *..SomeService.doThird(..))")
public void myAfterThrowing(){
System.out.println("执行异常通知方法");
}
@AfterThrowing(value="execution(* *..SomeService.doThird(..))",throwing="ex")
public void myAfterThrowing(Exception ex){
System.out.println("执行异常通知方法 ex = " + ex.getMessage());
}
@After("execution(* *..SomeService.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知方法");
}
}
定义切入点
通过上面的代码我们可以看到名称表达式execution()有大量冗余,可以通过定义切入点解决。
package com.hnu.service;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect //表示当前类是切面
public class MyAspect {
@After("doThirdPointcut()") //第二部:直接使用这个切入点
public void myAfter(){
System.out.println("执行最终通知方法");
}
//第一步:定义了一个切入点,叫doThirdPointcut()
@Pointcut("execution(* *..SomeService.doThird(..))")
public void doThirdPointcut(){}
}
基于XML的实现:
编写切面类(是一个POJO类),把上面的切面类删除所有的注解即可
package com.hnu.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
public void myBefore(){
System.out.println("执行前置通知方法");
}
public void myBefore(JoinPoint jp){
System.out.println("执行前置通知方法 jp = " + jp);
}
public void myAfterReturning(){
System.out.println("执行后置通知方法");
}
//注意这里虽然能获取方法返回值,但是不能修改
public void myAfterReturning(Object result){
System.out.println("执行后置通知方法 result= " + result);
}
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("执行环绕通知方法,目标方法执行之前");
Object result = pjp.proceed();
System.out.println("执行环绕通知方法,目标方法执行之后");
if(result != null){ //可以修改目标方法的返回结果
result = ((String)result).toUpperCase();
}
return result;
}
public void myAfterThrowing(){
System.out.println("执行异常通知方法");
}
public void myAfterThrowing(Exception ex){
System.out.println("执行异常通知方法 ex = " + ex.getMessage());
}
public void myAfter(){
System.out.println("执行最终通知方法");
}
}
下面是关键的XML配置
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册切面 -->
<bean id="myAspect" class="com.hnu.service.MyAspect"/>
<!-- 注册目标对象 -->
<bean id="someService" class="com.hnu.service.SomeServiceImpl"/>
<!-- AOP配置 -->
<aop:config>
<aop:pointcut expression="execution(* *..SomeService.doFirst())" id="doFirstPointcut"/>
<aop:pointcut expression="execution(* *..SomeService.doSecond())" id="doSecondPointcut"/>
<aop:pointcut expression="execution(* *..SomeService.doThird())" id="doThirdPointcut"/>
<aop:aspect ref="myAspect">
<aop:before method="myBefore" pointcut="execution(* *..SomeService.doFirst())"/> <!-- 不使用自定义切入点写法 -->
<aop:before method="myBefore(org.aspectj.lang.JoinPoint)" pointcut-ref="doFirstPointcut"/>
<aop:after-returning method="myAfterReturning" pointcut-ref="doSecondPointcut"/>
<aop:after-returning method="myAfterReturning(java.lang.Object)" pointcut-ref="doSecondPointcut" returning="result"/>
<aop:around method="myAround" pointcut-ref="doSecondPointcut"/>
<aop:after-throwing method="myAfterThrowing" pointcut-ref="doThirdPointcut"/>
<aop:after-throwing method="myAfterThrowing(java.lang.Exception)" pointcut-ref="doThirdPointcut" throwing="ex"/>
<aop:after method="myAfter" pointcut-ref="doThirdPointcut"/>
</aop:aspect>
</aop:config>
</beans>