接上篇beans部分,这部分说下aop的配置。
AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论。是对传统OOP的补充。
AOP的主要编程对象是切面(aspect),而切面模块化横切点。
在应用AOP编程时,仍需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样一来横切关注点就被模块化到特殊的独享(切面)里。
AOP的好处:
每个业务逻辑唯一一个位置,代码不分散,便于维护和升级。
业务模块简洁,值包含核心业务代码。
AOP术语:
切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象。
通知(Advice):切面必须要完成的工作。
目标(Target):被通知的对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
连接点(JoinPoint):程序执行的某个特定位置。如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如ArithmeticCalculator#add()方法执行前的连接点:执行点为ArithmeticCalculator#add()方位为该方法执行前的位置。
切点(pointcut):每个类都拥有多个连接点。例如:ArithmeticCalculator的所有方法实际上都是连接点,即连接点是程序类中可观存在的事物。AOP通过切点定位到特点的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。起点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframeword.Pointcut接口描述,它使用类和方法作为连接点的查询条件。
AspectJ:Java社区中最完整最流行的AOP框架。
在Spring中启用AspectJ注解支持;
要在Spring应用中应用AspectJ注解,必须在classpath下包含AspectJ类库:aopalliance.jar、aspectj.weaver.jar和Spring-aspects.jar.
将aop scheme添加到<beans>根元素中。
要在SpringIOC容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个空的XM元素<aop:aspectj-autoproxy>
当SpringIOC容器侦测到Bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动与AspectJ切面匹配的Bean创建代理。
用AspectJ注解声明切面
要在Spring中声明AspectJ切面,只需要在IOC容器中将切面生面为Bean实例,当在SpringIOC容器中初始化AspectJ切面之后,SpingIOC容器就会为那些与AspectJ切面相匹配的Bean创建代理。
在AspectJ注解中。切面只是一个带有@Aspect注解的Java类。
通知是标注有某种注解的简单的java方法。
AspectJ支持5种类型的通知注解:
@Before:前置通知,在方法执行之前执行
@After:后置通知,在方法执行之后执行
@AfterRunning:返回通知,在方法返回结果之后执行
@AfterThrowing:异常通知,在方法抛出异常之后执行
@Around:环绕通知,围着着方法执行
利用方法签名编写AspectJ切入点表达式
最典型的切入点表达式是根据犯法的签名来匹配各种方法:
execution * com.atguigu.spring.ArithmeticCalculator.*(...):匹配ArithmeticCalculator中声明的所有方法,第一个*表示任意修饰符机返回值。第二个*代表任意方法。...匹配任意数量的参数,若目标类与接口与接口与该切面在一个包中,可以省略包名。
execution public * com.atguigu.spring.ArithmeticCalculator.*(...):匹配ArithmeticCalculator接口中所有public的方法。
execution public double com.atguigu.spring.ArithmeticCalculator.*(...):匹配ArithmeticCalculator接口中返回double类型的方法
execution public double com.atguigu.spring.ArithmeticCalculator.*(.double, ..):匹配第一个参数为double类型的方法,..匹配任意数量任意类型的参数。
execution public double com.atguigu.spring.ArithmeticCalculator.*(double, double):匹配参数类型为double,double类型的方法。
在AspectJ中,切入点表达式可以通过操作符&&,||,!结合起来。
可以在通知方法中声明一个类型为JoinPoint的参数。然后就能访问连接细节。如方法名和参数值。
对于环绕通知来说连接点的参数类型必须是ProceedingJoinPoint。他是JoinPoint的子接口,允许控制何时执行,是否执行连接点。
在环绕通知中,需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法,如果忘记这样做就会导致通知被执行了,但是目标方法没有被执行。
注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用jointPoint.proceed()的返回值,否则会出现空指针异常。
指定切面优先级: 可以使用@Order注解来指定切面的优先级,值越小优先级越高
重用切入点定义:通过@Pointcut注解将一个切入点声明称简单的方法,切入点的方法体通常是空的。切入点方法的访问控制同时也控制着这个切入点的可见性。如果切入点要在多个切面找那个共用,最好将他们几种在一个公共类中。在引入切入点时,必须将类名也包括在内,如果类没有与这个切面放在同一个包中,还必须包含包名。
基于注解的aop示例:
public interface ArithmeticCalculator {
int add(int i ,int j);
int sub(int i,int j );
int mul(int i,int j);
int div (int i,int j);
}
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
@Order(1)
@Component
@Aspect
public class ValidationAspect {
/*@Before("execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")*/
@Before("LoggingAspect.declareJoinPoint()")
public void validate(JoinPoint joinPoint){
System.out.println("--> validation" + Arrays.asList(joinPoint.getArgs()));
}
}
package com.atguigu.spring.aop.impl;
import java.util.Arrays;
import java.util.List;
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.core.annotation.Order;
import org.springframework.stereotype.Component;
//把这个类声明为一个切面:首先把这个类放到IOC容器中、在声明为一个切面
//然后在考虑在哪个类的哪个方法执行前插入通知的方法。
/**
* 可以使用@Order注解来指定切面的优先级,值越小优先级越高
* @author adminitrator
*
*/
@Order(2)
@Component
@Aspect
public class LoggingAspect {
/**
* 定义一个方法,用于声明切入点表达式,一般地,该方法中不需要添加其他代码
* 使用@Pointcut注解来声明切入点表达式
* 后面的其他同志直接使用方法名来引用当前的额切入点表达式。
*/
@Pointcut("execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public void declareJoinPoint(){
}
//声明该方法是一个前置通知:在目标方法之前执行
//注解中的* 表示ArithmeticCalculator接口下的所有方法
//注解参数中的 public int 也可也用* 来代替,表示任意修饰符,任意返回类型
@Before("declareJoinPoint()")
public void beforeMethod(JoinPoint joinPoint){//joinPoint 连接点
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+ methodName+" begins with "+ args);
}
//后置通知:在目标方法执行之后(无论是否发生异常),执行的通知
//后置通知中还不能访问目标方法执行的结果
@After(value = "declareJoinPoint()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+ methodName+" begins with "+ args);
}
//在方法正常返回结束后执行的代码
//放回通知是可以范文到方法的返回值的
@SuppressWarnings("unused")
@AfterReturning(value = "execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))",
returning="result")
public void afterReturning(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+ methodName+ " result-->" +result);
}
//异常通知
/**
* 在目标方法出现异常时,会执行代码
* 可以方位到异常对象;可以指定出现特定异常时在执行通知。通诺throwing参数中异常,在方法参数中执行异常类型。
* @param joinPoint
* @param ex
*/
@SuppressWarnings("unused")
@AfterThrowing(value = "execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))",throwing="ex")
public void afterThrowing(JoinPoint joinPoint,Exception ex){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+ methodName +" occurs exception " + ex);
}
//环绕通知 就相当于动态代理
/**
* 环绕通知需要携带ProceedingJoinPoint类型参数
* 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法
* 且环绕通知必须有返回值,返回值即为目标方法的返回值。
* @param joinPoint
*/
/* @Around(value = "execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public Object aroundMethod(ProceedingJoinPoint pjd){
//执行目标方法
Object result = null;
try {
System.out.println("The method " + pjd.getSignature().getName()+ " begin " + Arrays.asList(pjd.getArgs()));
result = pjd.proceed();
//后置通知
System.out.println("The method " + pjd.getSignature().getName()+ " ends " +Arrays.asList(pjd.getArgs()));
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
//异常通知
System.out.println("The method " + pjd.getSignature().getName()+ " occurs " + e);
throw new RuntimeException(e);
}
//后置通知
System.out.println("The method " + pjd.getSignature().getName()+ " ends");
return result;
}*/
}
<?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:context="http://www.springframework.org/schema/context"
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">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.atguigu.spring.aop.impl" />
<!-- 这句话的作用是使我们在切面声明的注解(如:before)起作用:在我调用目标方法跟before注解中配置的方法相匹配的时候,aop自动为那个目标方法所在的类生成代理对象,在调用方法之前执行配置的方法。 -->
<!-- 使AspectJ注解起作用:自动为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
基于XML的AOP配置:
public interface ArithmeticCalculator {
int add(int i ,int j);
int sub(int i,int j );
int mul(int i,int j);
int div (int i,int j);
}
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
public class LoggingAspect {
public void declareJoinPoint() {
}
public void beforeMethod(JoinPoint joinPoint) {// joinPoint 连接点
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " begins with " + args);
}
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " begins with " + args);
}
@SuppressWarnings("unused")
public void afterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " result-->" + result);
}
@SuppressWarnings("unused")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " occurs exception " + ex);
}
public Object aroundMethod(ProceedingJoinPoint pjd) {
// 执行目标方法
Object result = null;
try {
System.out.println("The method " + pjd.getSignature().getName() + " begin " + Arrays.asList(pjd.getArgs()));
result = pjd.proceed();
// 后置通知
System.out.println("The method " + pjd.getSignature().getName() + " ends " + Arrays.asList(pjd.getArgs()));
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
// 异常通知
System.out.println("The method " + pjd.getSignature().getName() + " occurs " + e);
throw new RuntimeException(e);
}
// 后置通知
System.out.println("The method " + pjd.getSignature().getName() + " ends");
return result;
}
}
public class ValidationAspect {
public void validate(JoinPoint joinPoint){
System.out.println("--> validation" + Arrays.asList(joinPoint.getArgs()));
}
}
<?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:context="http://www.springframework.org/schema/context"
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">
<!-- arithmeticCalculatorImpl 纳入IOC容器管理 -->
<bean id="arithmeticCalculatorImpl" class="com.atguigu.spring.aop.xml.ArithmeticCalculatorImpl"></bean>
<!-- 配置切面的Bean -->
<bean id="loggingAspect" class="com.atguigu.spring.aop.xml.LoggingAspect"></bean>
<bean id="validationAspect" class="com.atguigu.spring.aop.xml.ValidationAspect"></bean>
<!--配置aop -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.atguigu.spring.aop.xml.ArithmeticCalculator.*(int, int))" id="pointcut"/>
<!-- 配置切面和通知 -->
<aop:aspect ref="loggingAspect" order="2" >
<!-- <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/> -->
<aop:around method="aroundMethod" pointcut-ref="pointcut" />
</aop:aspect>
<aop:aspect ref="validationAspect" order="1">
<aop:before method="validate" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>