在上一篇中,介绍了使用代理来实现日志的记录,该方法在平时工作中不易于使用,因为要有一定的设计模式的基础。下面就来介绍下Spring的一个非常核心的概念AOP,即面向切面编程。
为了理解AOP,必须先了解AOP的相关术语:
1、通知(Advice):
在AOP中,切面的工作被称为通知。通知定义了切面“是什么”以及“何时”使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。
Spring切面可以应用5种类型的通知:
(1)前置通知(Before):前置通知, 在方法执行之前执行; (2)后置通知(After):后置通知, 在方法执行之后执行 ; (3)返回通知(After-returning):返回通知, 在方法返回结果之后执行; (4)异常通知(After-throwing):异常通知, 在方法抛出异常之后; (5)环绕通知(Around):环绕通知, 围绕着方法执行;
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。
如果说通知定义了切面“是什么”和“何时”的话,那么切点就定义了“何处”。比如我想把日志引入到某个具体的方法中,这个方法就是所谓的切点。
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容―――它是什么,在何时和何处完成其功能。
com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar
<!-- 自动扫描的包 --> <context:component-scan base-package="com.scorpios.spring.aop.impl"></context:component-scan> <!-- 使 AspectJ 的注解起作用 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
组件扫描(component scanning):Spring 能够从classpath下自动扫描, 侦测和实例化具有特定注解的组件。
特定组件包括:
@Component: 基本注解, 标识了一个受Spring管理的组件 @Respository: 标识持久层组件 @Service: 标识服务层(业务层)组件 @Controller: 标识表现层组件
对于扫描到的组件, Spring有默认的命名策略: 使用非限定类名, 第一个字母小写。 也可以在注解中通过value属性值标识组件的名称
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(2) @Aspect @Component public class LoggingAspect { /** * 定义一个方法,用于声明切入点表达式,一般的,该方法中不再需要其他的代码 使用@Pointcut来声明切入点表达式 * 后面的其他通知直接使用方法名来引用当前的切入点表达式。 */ @Pointcut("execution(public * com.scorpios.spring.aop.impl.ArithmeticCalculator.*(..))") public void declareJoinPointExpression() { } // 声明该方法是一个前置通知:在目标方法开始之前执行 @Before("declareJoinPointExpression()") public void BeforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("[BeforeMethod] the method " + methodName + " begins with " + args); } // 后置通知:在目标方法执行后(无论是否发生异常),执行的通知 // 在后置通知中还不能访问目标方法执行的结果 @After("declareJoinPointExpression()") public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("[afterMethod] the method " + methodName + " ends."); } // 在方法正常结束时执行的代码 // 返回通知时可以访问到方法的返回值的! @AfterReturning(value = "declareJoinPointExpression()", returning = "result") public void afterReturing(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("[afterReturing] the method " + methodName + " ends with :" + result); } // 在目标方法出现异常时会执行的代码 // 可以访问到异常对象;且可以指定在出现特定异常时在执行通知代码 @AfterThrowing(value = "declareJoinPointExpression()", throwing = "ex") public void afterThrowing(JoinPoint joinPoint, Exception ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("[afterThrowing] the method " + methodName + " occurs with :" + ex); } // 环绕通知需要携带ProceedingJoinPoint类型的参数 // 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法 // 且环绕通知必须有返回值,返回值即为目标方法的返回值 @Around("declareJoinPointExpression()") public Object aroundMethod(ProceedingJoinPoint pjp) { Object result = null; String methodName = pjp.getSignature().getName(); try { // 前置通知 System.out.println( "[aroundMethod before] the method " + methodName + " begins with " + Arrays.asList(pjp.getArgs())); result = pjp.proceed(); // 返回通知 System.out.println("[aroundMethod returning] the method ends with " + result); } catch (Throwable e) { // 异常通知 System.out.println("[aroundMethod exception] the method " + methodName + "occurs exception:" + e); } // 后置通知 System.out.println("[aroundMethod after] the method " + methodName + " ends"); return result; } }
public class Main { public static void main(String[] args) { ApplicationContext apx = new ClassPathXmlApplicationContext("applicationContext.xml"); ArithmeticCalculator arithmeticCalculator = apx.getBean(ArithmeticCalculator.class); int result = arithmeticCalculator.add(2, 8); System.out.println("-->" + result); } }
@Order(1) @Aspect @Component public class ValidateAspect { // 声明该方法是一个前置通知:在目标方法开始之前执行 @Before("LoggingAspect.declareJoinPointExpression()") public void ValidateBefore(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("**** validate " + args); } }
1、引入jar包;
2、在Spring的配置文件中加入aop的命名空间。
3、在配置文件中配置自动扫描的包:
<context:component-scan base-package="com.scorpios.spring.aop.*"></context:component-scan>
4、加入使 AspjectJ 注解起作用的配置:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>为匹配的类自动生成动态代理对象.
5、编写切面类:
6、配置切面
a.切面必须是IOC中的bean: 实际添加了@Component注解 b.声明是一个切面: 通过添加@Aspect注解声明一个bean是一个切面! c.声明通知: 即额外加入功能对应的方法. 前置通知: @Before("execution(public int com.scorpios.spring.aop.ArithmeticCalculator.*(int, int))") @Before 表示在目标方法执行之前执行 @Before 标记的方法的方法体. @Before 里面的是切入点表达式:
7、在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数.
关于切入点表达式的解释:
如图所示,我们使用execution()指示器选择UserServiceImpl的sayHello方法。方法表达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号(..)表明切点要选择任意的sayHello()方法,无论该方法的入参是什么。