AOP

AOP(Aspect Oriented Programming 面向切面编程)是一种指在程序运行期间动态的将某段代码切入到指定方法的指定位置进行运行的编程方式,这种编程方式实现了代码复用,是对传统OOP(Object Oriented Programming,面向对象编程 )的补充。目前,Aspectj是Java社区里最完整最流行的AOP框架,在Spring 2.0以上版本中可以通过Aspectj注解或基于XML配置AOP。

AOP常用注解@Aspect,@Component,@Before,@After,@AfterReturning,@AfterThrowing,@Around,@Pointcut,@Order

怎样使用AOP:

例:

package com.jd.calculator;

import org.springframework.stereotype.Service;

@Service
public class CalculatorService implements ICalculatorService {
     
	@Override
	public int mul(int a, int b) {
		int result = a*b;
		return result;
	}
     
	@Override
	public int div(int a, int b) {
		int result = a/b;
		return result;
	}
}

 

package com.jd.calculator;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect//将该类声明为切面类
@Component//将该类对象放入IOC容器
public class CalculatorAspect {
    
	//前置增强(又称前置通知):在目标方法执行之前执行
	@Before("execution(public int com.jd.calculator.CalculatorService.*(..))")
	public void before(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		Object[] params = joinPoint.getArgs();
		
		System.out.println(target.getClass().getName()+":The "+methodName+" method begins.");
		System.out.println(target.getClass().getName()+":Parameters of the "+methodName+" method: ["+params[0]+","+params[1]+"]");
	}
     
	//后置增强(又称后置通知):在目标方法执行后执行,无论目标方法运行期间是否出现异常。注意:后置增强无法获取目标方法执行结果,可在返回增强中获取
	@After("execution(public int com.jd.calculator.CalculatorService.*(..))")
	public void after(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		System.out.println(target.getClass().getName()+":The "+methodName+" method ends.");
	}
     
	//返回增强(又称返回通知):在目标方法正常结束后执行,可以获取目标方法的执行结果
	@AfterReturning(value="execution(public int com.jd.calculator.CalculatorService.*(..))",returning="result")
	public void afterReturning(JoinPoint joinPoint,Object result) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		System.out.println(target.getClass().getName()+":Result of the "+methodName+" method:"+result);
	}
	
	//异常增强(又称异常通知):目标方法抛出异常之后执行,可以访问到异常对象,且可以指定在出现哪种异常时才执行增强代码
	@AfterThrowing(value="execution(public int com.jd.calculator.CalculatorService.*(..))",	throwing="e")
	public void afterThrowing(JoinPoint joinPoint,Exception e) {//e变量数据类型为Exception,ArithmeticException异常时Exception的子类,所以目标方法出现异常时afterThrowing方法可以执行;如果e的数据类型为NullPointerException,则目标方法出现异常后afterThrowing方法不会执行
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		System.out.println(target.getClass().getName()+":Exception of the method "+methodName+": "+e);
	}
}

Spring配置文件添加<aop:aspectj-autoproxy/>,该配置作用:如果创建目标对象的目标类中的方法与AspectJ切面中切入点表达式匹配,则自动为该目标对象生成动态代理对象,该代理对象默认使用JDK动态代理,当配置为<aop:aspectj-autoproxy poxy-target-class="true"/>时,则使用CGLib动态代理:

<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

测试代码:

JDK代理类是在生成的代理类的内部创建目标类或接口的实现类,此时创建代理对象的代理类与创建目标对象的类之间没有继承关系,所以不能写成applicationContext.getBean(CalculatorService.class),但是由于此时创建代理对象的代理类实现了ICalculatorService接口,所以可以写成applicationContext.getBean(ICalculatorService.class),

当proxy-target-class标签属性为"true"表示使用CGLib生成代理对象,此时创建代理对象的代理类继承创建目标对象的类,所以可以写作applicationContext.getBean(CalculatorService.class);

package com.jd.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.jd.calculator.ICalculatorService;

public class Test {
     
	public static void main(String[] args) {
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
		
		ICalculatorService calculatorService = applicationContext.getBean(ICalculatorService.class);// 从IOC容器获取对象,由于已经为CalculatorService对象创建了代理对象,所以此时获取的对象为代理对象
		
		Class<?> clazz = calculatorService.getClass();//获取创建代理对象的代理类对应的Class对象
		System.out.println(clazz.getName());
		
		Class<?> [] interfaces = clazz.getInterfaces();
		for (Class<?> interfase : interfaces) {//查看创建代理对象的代理类实现了哪些接口
			System.out.println(interfase.getName());
		}
		
		int result = calculatorService.mul(1, 1);
		System.out.println("-->"+result);
		
		result = calculatorService.div(1, 0);
		System.out.println("-->"+result);
		
		applicationContext.close();
	}
}

执行过程:

try {
	try {
		doBefore();// @Before注解所修饰的方法
		method.invoke();// 执行目标对象内的方法
	} finally {
		doAfter();// @After注解所修饰的方法
	}
	doAfterReturning();// @AfterReturning注解所修饰的方法
} catch (Exception e) {
	doAfterThrowing();// @AfterThrowing注解所修饰的方法
}

通过@Around注解可以实现@Before,@After,@AfterReturning和@AfterThrowing增强效果,代码如下:

package com.jd.calculator;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class CalculatorAspect {
	
	//环绕增强:目标方法执行前后都可以织入增强处理
	@Around("execution(public int com.jd.calculator.CalculatorService.*(..))")
	public Object around(ProceedingJoinPoint joinPoint) {
		Object result = null;
		Object target=joinPoint.getTarget();//目标对象
		String methodName = joinPoint.getSignature().getName();
		Object[] params = joinPoint.getArgs();
          
		try {
			try {
				//前置增强
				System.out.println(target.getClass().getName()+":The "+methodName+" method begins.");
				System.out.println(target.getClass().getName()+":Parameters of the "+methodName+" method: ["+params[0]+","+params[1]+"]");
				//执行目标对象内的方法
				result = joinPoint.proceed();
			}finally {
				//后置增强
				System.out.println(target.getClass().getName()+":The "+methodName+" method ends.");
			}
			//返回增强
			System.out.println(target.getClass().getName()+":Result of the "+methodName+" method:"+result);
		} catch (Throwable e) {
			//异常增强
			System.out.println(target.getClass().getName()+":Exception of the method "+methodName+": "+e);
		}
		
		return result;
	}
}

通过观察发现CalculatorAspect类中@Before,@After,@AfterReturning、@AfterThrowing和@Around注解中切入点表达式相同,为了简化代码,可以单独自定义一个@Pointcut注解修饰的空方法,通过该方法可以简化@Before,@After,@AfterReturning、@AfterThrowing和@Around注解中的切入点表达式,具体代码如下:

package com.jd.calculator;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class CalculatorAspect {
	
	@Pointcut("execution(public int com.jd.calculator.CalculatorService.*(..))")
	public void pointCut() {
		
	}
     
	//前置增强
	@Before("pointCut()")
	public void before(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		Object[] params = joinPoint.getArgs();
		
		System.out.println(target.getClass().getName()+":The "+methodName+" method begins.");
		System.out.println(target.getClass().getName()+":Parameters of the "+methodName+" method: ["+params[0]+","+params[1]+"]");
	}
	
	//后置增强
	@After("pointCut()")
	public void after(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		System.out.println(target.getClass().getName()+":The "+methodName+" method ends.");
	}
	
	//返回增强
	@AfterReturning(pointcut="pointCut()", returning="result")
	public void afterReturning(JoinPoint joinPoint,Object result) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		System.out.println(target.getClass().getName()+":Result of the "+methodName+" method:"+result);
	}
	
	//异常增强
	@AfterThrowing(pointcut="pointCut()", throwing="e")
	public void afterThrowing(JoinPoint joinPoint,Exception e) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		System.out.println(target.getClass().getName()+":Exception of the method "+methodName+": "+e);
	}
}

通过XML配置AOP是Spring框架专有的,但由于AspectJ得到越来越多的AOP框架支持,所以通过注解实现AOP将会有更多重用的机会。通过XML配置实现AOP代码如下:

package com.jd.calculator;

import org.aspectj.lang.JoinPoint;

public class CalculatorAspect {

	//前置增强
	public void before(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		Object[] params = joinPoint.getArgs();
		
		System.out.println(target.getClass().getName()+":The "+methodName+" method begins.");
		System.out.println(target.getClass().getName()+":Parameters of the "+methodName+" method: ["+params[0]+","+params[1]+"]");
	}
	
	//返回增强
	public void afterReturning(JoinPoint joinPoint,Object result) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		System.out.println(target.getClass().getName()+":Result of the "+methodName+" method:"+result);
	}
	
	//异常增强
	public void afterThrowing(JoinPoint joinPoint,Exception e) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		System.out.println(target.getClass().getName()+":Exception of the method "+methodName+": "+e);
	}
	
	//后置增强
	public void after(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		System.out.println(target.getClass().getName()+":The "+methodName+" method ends.");
	}
}
<?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: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-4.3.xsd
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
     
	<bean class="com.jd.calculator.CalculatorService"></bean>
     
	<bean id="calculatorAspect" class="com.jd.calculator.CalculatorAspect"></bean>
	
	<!-- 配置AOP -->
	<aop:config proxy-target-class="true">
		<!-- 配置切点表达式 -->
		<aop:pointcut expression="execution(public int com.jd.calculator.CalculatorService.*(..))" id="pointcut"/>
		<!-- 配置切面及增强类型:可以有多个切面,每个切面又可以配置多个增强类型-->
		<aop:aspect ref="calculatorAspect">
			<aop:before method="before" pointcut-ref="pointcut"/>
			<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
			<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
			<aop:after method="after" pointcut-ref="pointcut"/>
		</aop:aspect>
	</aop:config>
</beans>

如果一个方法匹配多个切面中相同类型增强方法,那么必须明确指定各个切面优先级,否则切面按照类名字典顺序执行,切面优先级的确定既可以通过在切面类添加@Order注解或实现Ordered接口实现,也可以在XML配置aop:aspect标签的order属性来实现。

方式一:@Order注解:

package com.jd.calculator;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Order(1)//值越小,优先级越高,越先执行
@Component
public class MethodAspect {

	@Before("execution(public int com.jd.calculator.CalculatorService.*(..))")
	public void before(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		
		System.out.println(target.getClass().getName()+":The "+methodName+" method begins.");
	}
}
package com.jd.calculator;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Order(2)//值越小,优先级越高,越先执行
@Component
public class ArgumentsAspect {

	@Before("execution(public int com.jd.calculator.CalculatorService.*(..))")
	public void before(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		Object[] params = joinPoint.getArgs();
		System.out.println(target.getClass().getName()+":Parameters of the "+methodName+" method: ["+params[0]+","+params[1]+"]");
	}
}

com.jd.calculator.CalculatorService类中每个public访问权限的方法都匹配MethodAspect切面类与ArgumentsAspect切面类内@Before注解修饰的方法,但由于MethodAspect切面优先级比ArgumentsAspect切面优先级高,所以执行com.jd.calculator.CalculatorService类mul方法时一定是MethodAspect切面类中前置增强先执行,而后ArgumentsAspect切面类中前置增强后执行。

方式二:XML文件配置:

package com.jd.calculator;

import org.aspectj.lang.JoinPoint;

public class MethodAspect {

	public void before(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		
		System.out.println(target.getClass().getName()+":The "+methodName+" method begins.");
	}
}
package com.jd.calculator;

import org.aspectj.lang.JoinPoint;

public class ArgumentsAspect {
     
	public void before(JoinPoint joinPoint) {
		Object target=joinPoint.getTarget();
		String methodName = joinPoint.getSignature().getName();
		Object[] params = joinPoint.getArgs();
		System.out.println(target.getClass().getName()+":Parameters of the "+methodName+" method: ["+params[0]+","+params[1]+"]");
	}
}
<?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: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-4.3.xsd
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
     
	<bean class="com.jd.calculator.CalculatorService"></bean>
	
	<bean id="methodAspect" class="com.jd.calculator.MethodAspect"></bean>
	<bean id="argumentsAspect" class="com.jd.calculator.ArgumentsAspect"></bean>
	
	<!-- 配置AOP -->
	<aop:config proxy-target-class="true">
		<!-- 配置切点表达式 -->
		<aop:pointcut expression="execution(public int com.jd.calculator.CalculatorService.*(..))" id="pointcut"/>
		<!-- 配置切面及增强类型 -->
		<aop:aspect ref="argumentsAspect" order="2">
			<aop:before method="before" pointcut-ref="pointcut"/>
		</aop:aspect>
		
		<aop:aspect ref="methodAspect" order="1">
			<aop:before method="before" pointcut-ref="pointcut"/>
		</aop:aspect>
	</aop:config>
</beans>

 

上一篇:后端笔记07-AopLog


下一篇:Spring AOP