一、AOP基础

1.基本需求

    

需求: 日志功能,在程序执行期间记录发生的活动。

ArithmeticCalculate.java

public interface ArithmeticCalculate{

	public int add(int a,int b);

	public int sub(int a,int b);

	public int mul(int a,int b);

	public int div(int a,int b);
}

ArithmeticCalculateImpl.java

public class ArithmeticCalculateImpl implements ArithmeticCalculate{

	@Override
public int add(int a,int b){
System.out.println("The method add.....begin");
int result = a + b;
System.out.println("The method add.....end");
return result;
} @Override
public int sub(int a,int b){
System.out.println("The method sub.....begin");
int result = a - b;
System.out.println("The method sub.....end");
return result;
} @Override
public int mul(int a,int b){
System.out.println("The method mul.....begin");
int result = a * b;
System.out.println("The method mul.....end");
return result;
} @Override
public int div(int a,int b){
System.out.println("The method div.....begin");
int result = a / b;
System.out.println("The method div.....end");
return result;
} }

以上这样写会出现两种问题。

(1)代码混乱

     越来越多的非业务需求加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。    

(2)代码分散

     以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块重复相同的代码日志,如果日志需求发生改变还得修改所有的需求。

 

使用动态代理

原理: 使用一个代理将对象包装起来,然后改代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

ArithmeticCalculateProxy.java

public class ArithmeticCalculateProxy{

	//要代理的对象
private ArithmeticCalculate target; public ArithmeticCalculateProxy(){
} public ArithmeticCalculateProxy(ArithmeticCalculate target){
this.target = target;
} public ArithmeticCalculate getProxy(){
ArithmeticCalculate proxy = null; //代理对象由哪一个类加载器负责加载
ClassLoader loader = target.getClass().getClassLoader(); //代理对象的类型,即有哪些方法
Class[] interfaces = new Class[]{ArithmeticCalculate.class}; //当调用代理对象其中方法时,该执行的代码
InvocationHandler handler = new InvocationHandler(){ /*
* proxy: 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用
* method: 正在被调用的方法
* args:调用方法时传入的参数
*/
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{ String methodName = method.getName(); //日志
System.out.println("The method " + methodName +" begin......");
//执行方法
Object result = method.invoke(target,args);
//日志
System.out.println("The method " + methodName +" end......");
return result;
}
}; proxy = (ArithmeticCalculate)Proxy.newProxyInstance(loader,interfaces,handler); return proxy;
}
}

Test.java

	@Test
public void testCalculate(){
ArithmeticCalculate target = new ArithmeticCalculateImpl();
ArithmeticCalculate proxy = new ArithmeticCalculateProxy(target).getProxy();
System.out.println(proxy.add(4,2));
System.out.println(proxy.sub(4,2));
}

结果:

      The method add begin......

      The method add end......

      6

      The method sub begin......

      The method sub end......

      2

 

2.AOP简介

AOP(Aspect-Oriented Programming): 面向切面编程,而切面模块化横切关注点。

在AOP编程中,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类。这样横切关注点就被模块化到特殊的对象(切面)里。

    

    AOP希望将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,还业务逻辑类一个清新的世界。我们知道将这些重复性的横切逻辑独立出来很容易,但是将这些独立的逻辑融合到业务逻辑中完成和原来一样的业务操作,这才是事情的关键,也是AOP要解决的主要问题。

使用AspectJ解决以上问题

LoggerAspect.java

/*
* 把这个类声明为一个切面
* 1.把该类放到IOC容器中
* 2.再声明为切面
*/
@Aspect
@Component
public class LoggerAspect{ /*
* 声明该方法是一个前置通知
* 在目标方法开始之前执行
*/
@Before("execution(* com.kiwi.aop.ArithmeticCalculate.*add(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName +" begin......");
}
}

applicationContext.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:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 指定Spring IOC容器扫描的包 -->
<context:component-scan base-package="com.kiwi"/> <!-- 使AspectJ注解起作用,为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy />
</beans>

Test.java

	@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculate ac = context.getBean(ArithmeticCalculate.class);
System.out.println(ac.add(2,2));
}

结果:

      The method add begin......

      4

 

二、使用AspectJ注解声明切面

(1)要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例. 当在Spring IOC容器中初始化AspectJ切面之后, Spring IOC容器就会为那些与AspectJ切面相匹配的Bean创建代理。

(2)在AspectJ注解中,切面只是一个带有@Aspect注解的Java类。

(3)AspectJ 支持 5 种类型的通知注解:

    @Before: 前置通知,在方法执行之前执行。

   @After: 后置通知,在方法执行之后执行。

   @AfterRunning: 返回通知,在方法返回结果之后执行。

   @AfterThrowing: 异常通知,在方法抛出异常之后。

   @Around: 环绕通知,围绕着方法执行。

 

1.切点表达式函数

(1)切点表达式由关键字和操作参数组成。如:execution(* greetTo(..))

     execution为关键字,代表目标执行某一方法。

     * greetTo(..)为操作数,描述目标方法的匹配模式串。

    两者联合起来表示目标类greetTo()方法的连接点,为了描述方便面我们将前者称为函数,将匹配串称为入参。

    

      

(2)在函数入参中使用通配符

     * : 匹配任意字符,但它只能匹配上下文中一个元素。

     .. : 匹配任意字符,可以匹配上下文多个元素,但在表示类的时候,必须和*联合使用,在表示入参的时候单独使用。

     + : 表示按类型匹配指定所以类,必须跟在类名后面。

 

2.前置通知

(1)在方法执行之前的通知。

(2)前置通知使用@Before注解,并将切入点表达式的值作为注解值。

3.后置通知

     (1)后置通知是在连接点完成之后执行的,无论是否抛异常都会执行。

     (2)后置通知中不能访问目标方法的执行的结果。

	/*
* 后置通知
* 在目标方法执行后,无论是否发生异常,都执行的通知。
*/
@After("execution(* com.kiwi.aop.ArithmeticCalculate.*(..))")
public void afterMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println("The method " + name +" end......");
}

 

4.返回通知

(1)无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点正常返回的时候执行,可以使用返回通知。

(2)在返回通知中,只要将returnning属性添加到@AfterReturning注解中,就可以访问连接点的返回值,该属性的值即为用来传入返回值的参数名称。

(3)必须在通知方法的签名中添加一个同名的参数,Spring AOP才会通过这个参数传递返回值。

	@AfterReturning(pointcut="execution(* com.kiwi.aop.ArithmeticCalculate.*(..))",returning="result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName();
System.out.println("The method " + name +" end......" + result);
}

 

5.异常通知

(1)只有在连接点抛出异常时才执行的异常通知。

(2)将throwing属性添加到@AfterThrowing注解中,也可以以访问连接点抛出的异常。

(3)如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行。

	@AfterThrowing(pointcut="execution(* com.kiwi.aop.ArithmeticCalculate.*(..))",throwing="ex")
public void afterReturningMethod(JoinPoint joinPoint,Exception ex){
String name = joinPoint.getSignature().getName();
System.out.println("The method " + name +" end......" + ex);
}

 

6.环绕通知

(1)环绕通知需要携带ProceedingJoinPoint类型的参数。

(2)在环绕通知中需要明确调用 ProceedingJoinPoint 的proceed()方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行。

(3)环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed()的返回值。

	/*
* 1.环绕通知需要携带ProceedingJoinPoint类型的参数
* 2.在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed()
* 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
* 3.环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed()的返回值
*/
@Around("execution(* com.kiwi.aop.ArithmeticCalculate.*(..))")
public Object aroundMethod(ProceedingJoinPoint point){
String name = point.getSignature().getName(); //执行目标方法,返回值为目标方法的返回值
Object result = null;;
try{
//前置通知
System.out.println("The method " + name +" begin......" + Arrays.asList(point.getArgs()));
result = point.proceed();
//返回通知
System.out.println("The method " + name +" ends With......" + result);
}catch(Throwable e){
//异常通知
System.out.println("The method " + name +" occur Exception......");
throw new RuntimeException();
}
//后置通知
System.out.println("The method " + name +" end......"); return result;
}

 

7.切面的优先级

(1)在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的。

(2)切面的优先级可以通过实现Ordered接口或利用@Order注解指定。

(3)实现 Ordered 接口,getOrder() 方法的返回值越小,优先级越高。

(4)若使用 @Order 注解,序号出现在注解中。

    

 

8.重用切点

(1)编写AspectJ切面时, 可以直接在通知注解中书写切入点表达式. 但同一个切点表达式可能会在多个通知中重复出现。

(2)在AspectJ切面中, 可以通过@Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的。

    

Spring基础学习(四)—AOP的更多相关文章

  1. Python基础学习四

    Python基础学习四 1.内置函数 help()函数:用于查看内置函数的用途. help(abs) isinstance()函数:用于判断变量类型. isinstance(x,(int,float) ...

  2. Spring基础学习,附例子代码讲解

    什么是Spring.IOC.AOP.DI?     Spring是一个基于IOC和AOP的结构J2EE系统的框架.     IOC(Inversion Of Control)控制反转(Spring的基 ...

  3. spring基础学习01

    spring基础 Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用 IOC控制反转 把创建对象和维护对象之间的关系权利 ...

  4. spring框架学习(四)——注解方式AOP

    注解配置业务类 使用@Component("s") 注解ProductService 类 package com.how2java.service; import org.spri ...

  5. spring深入学习(四)-----spring aop

    AOP概述 aop其实就是面向切面编程,举个例子,比如项目中有n个方法是对外提供http服务的,那么如果我需要对这些http服务进行响应时间的监控,按照传统的方式就是每个方法中添加相应的逻辑,但是这些 ...

  6. spring基础学习---aop

    1:无参aop下面为项目结构 2:通知类.MyAdvice package cn.edu.aop; import org.aspectj.lang.ProceedingJoinPoint; //通知类 ...

  7. Spring学习笔记(一) Spring基础IOC、AOP

    1.       注入类型 a)       Spring_0300_IOC_Injection_Type b)       setter(重要) c)       构造方法(可以忘记) d)     ...

  8. spring基础学习

    ClassXmlAplicationContext和FileSystemXmlApplicationContext的区别      https://www.cnblogs.com/sxdcgaq808 ...

  9. Spring基础学习笔记

    1. Spring入门 1. 1 Spring的体系结构 1.2 HelloWorld 1.2.1 Maven的使用 1)maven 的javase工程目录结构: 2)maven的settings.x ...

随机推荐

  1. 关于Monkey的一切都在这里

    关于Monkey的一切都在这里 版权声明: 本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有. 允许有条件转载,转载请附带底部二维码. 一.什么是Monkey Monkey是 ...

  2. 利用jink的驱动软件j-flash 合并两个hex的方法,bootloader+app

    由于前几天要给工厂app和bootloader的hex的文件,网上很多都是bin的合并方法,bin的方法不再赘述,相信大家都能找到,现在将hex合并的方法写下来: 第一步:先打开第一个hex文件, 第 ...

  3. Java程序员入门:Java程序员面试失败的5大原因

    1 说得太少 尤其是那些开放式的问题,如"请介绍下你自己"或"请讲一下你曾经解决过的复杂问题".面试官会通过你对这些技术和非技术问题的回答来评估你的激情.他们也 ...

  4. jquery.vilidate的运用

    vilidate是jquery的一个form表单验证插件非常实用 里面需要注意的就是remote的用法 /*验证*/$().ready(function() {    $(".form_al ...

  5. 彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)

    本文同步自我的个人博客: http://mly-zju.github.io/ 众所周知javascript语言的一大特色就是异步,这既是它的优点,同时在某些情况下也带来了一些的问题.最大的问题之一,就 ...

  6. BUG,带给我的思考

    今天打开EverNote时,翻到了四年前在anjuke时做的一些bug分析总结.现在回过头看看也是有些价值所在,挑选出部分bug分享,希望能有所启发. 一. iOS新房APP4.4由于在91市场进行试 ...

  7. 2016: [Usaco2010]Chocolate Eating

    2016: [Usaco2010]Chocolate Eating Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 224  Solved: 87[Su ...

  8. 算法模板——AC自动机

    实现功能——输入N,M,提供一个共计N个单词的词典,然后在最后输入的M个字符串中进行多串匹配(关于AC自动机算法,此处不再赘述,详见:Aho-Corasick 多模式匹配算法.AC自动机详解.考虑到有 ...

  9. UISearchController 搜索

    UISearchController实现搜索 通过 UISearchController 实现 UISearchResultsUpdating 这个委托实现上面的效果: 视图中中需要声明UISearc ...

  10. PPAPI VS NPAPI

    flash player PPAPI 它的CPU和内存占用率会比较高,主要是因为缓存大多放在内存里而不是硬盘上.   npapi的flash跟ppapi的flash基本是一样的,只不过ppapi插件都 ...