- 什么是AOP?
- AOP基本概念
- AOP使用--注解方式
- AOP使用--XML方式
- 实例--日志
写在最前面的(源码地址):
https://github.com/xc83415134/spring_aop_demo
一、什么是AOP?
AOP(Aspect Oriented Programmin)即面向切面编程(或者翻译成以切面为导向的编程模式?),一种OOP延续的编程思想,将系统中非核心业务提取出来,从而将其与其所影响的对象解耦,切面就是提取出来的功能模块。切面可以帮助我们模块化横切关注点,常见的有日志、安全、事物等。
对于一个信用卡应用程序来说,存款、取款、帐单管理是它的主关注点,日志和持久化将成为横切整个对象结构的横切关注点。
二、AOP基本概念
以下为维基百科部分说明:
关注点(concern):对软件工程有意义的小的、可管理的、可描述的软件组成部分,一个关注点通常只同一个特定概念或目标相关联。 主关注点(core concern):一个软件最主要的关注点。 关注点分离(separation of concerns,SOC):标识、封装和操纵只与特定概念、目标相关联的软件组成部分的能力,即标识、封装和操纵关注点的能力。 方法(method):用来描述、设计、实现一个给定关注点的软件构造单位。 横切(crosscut):两个关注点相互横切,如果实现它们的方法存在交集。 支配性分解(dominant decomposition):将软件分解成模块的主要方式。传统的程序设计语言是以一种线性的文本来描述软件的,只采用一种方式(比如:类)将软件分解成模块;这导致某些关注点比较好的被捕捉,容易进一步组合、扩展;但还有一些关注点没有被捕捉,弥散在整个软件内部。支配性分解一般是按主关注点进行模块分解的。 横切关注点(crosscutting concerns):在传统的程序设计语言中,除了主关注点可以被支配性分解方式捕捉以外,还有许多没有被支配性分解方式捕捉到的关注点,这些关注点的实现会弥散在整个软件内部,这时这些关注点同主关注点是横切的。 侧面(aspect):在支配性分解的基础上,提供的一种辅助的模块化机制,这种新的模块化机制可以捕捉横切关注点。
从主关注点中分离出横切关注点是面向侧面的程序设计的核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过侧面来封装、维护,这样原本分散在在整个应用程序中的变动就可以很好的管理起来。
三、AOP使用--注解方式
1.启用AOP
以下为启用AspectJ自动代理,同时需声明Spring aop 命名空间(注意标红的部分)
<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:annotation-config /> <context:component-scan base-package="foo.bar"/> <!--启用aspectj自动代理--> <aop:aspectj-autoproxy /> </beans>
2.定义被监听类
普通的类,待监听对象无特殊
package foo.bar.observed; import org.springframework.stereotype.Component; /** * Created by xuc on 2018/1/16. * 说话的人A */ @Component public class HelloByAnnotation { public void sayHello(String arg) { System.out.println(arg); } }
3.定义切面
首先加入@Component注解,让spring扫描到,注入spring容器中。加入@Aspect注解,声明其为切面,再通过@Pointcut注解表面某一方法为切点,(括号内:execution表明为在方法执行时触发,*为返回任意类型,后面紧跟的为指定方法,String为接收参数类型,&&表示并且,arg为接收的参数)。其他注解:
注解 | 通知 |
@After | 通知方法在目标方法返回或抛出异常后调用 |
@AfterReturning | 通知方法在目标方法返回后调用 |
@AfterThrowing | 通知方法在目标方法抛出异常后 |
@Around | 通知方法在目标方法封装起来 |
@Before | 通知方法在目标方法调用前执行 |
package foo.bar.observer; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * Created by xuc on 2018/1/16. * * 大脑活动 * 执行顺序: * {@link Around} -> {@link Before} -> 目标方法 -> {@link Around} * -> {@link After} * -> {@link AfterReturning} 或 {@link AfterThrowing} */ @Component @Aspect public class brain { /** * 定义切点 * *:返回任意 * *.sayHello:指定方法 * String:指定接收类型 * arg:指定接收参数 */ @Pointcut("execution(* foo.bar.observed.HelloByAnnotation.sayHello(String)) && args(arg)") public void speak(String arg){} /** * 目标方法调用前执行 */ @Before("speak(arg)") public void think(String arg){ System.out.println("1.说话前要注意三思而后行:" + arg); } /** * 目标方法返回后执行 */ @AfterReturning("speak(arg)") public void listen(String arg){ System.out.println("2.说完后要虚心接受长辈的教诲"); } /** * 目标方法抛出异常后 */ @AfterThrowing("speak(arg)") public void reflection(String arg){ System.out.println("3.说错话后要反思为什么"); } /** * 目标方法前、后执行两次 */ @Around("speak(arg)") public void doThings(ProceedingJoinPoint joinPoint, String arg) throws Throwable { System.out.println("4.准备干点其他事"); joinPoint.proceed(); System.out.println("4.其他事做完"); } }
4.执行main测试(执行前,可以先看下第5步)
package foo.bar; import foo.bar.observed.HelloByAnnotation; import foo.bar.observed.IDeclareHello; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class HelloApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); excuteByAnnotion(context); } /** * 基于注解配置 * @param context */ private static void excuteByAnnotion(ApplicationContext context) { HelloByAnnotation helloByAnnotation = context.getBean(HelloByAnnotation.class); helloByAnnotation.sayHello("Hello world! -- by annotation"); IDeclareHello declareHello = (IDeclareHello)helloByAnnotation; declareHello.sayBye(); } }
5.通过注解引入新功能
通过aop对原类进行功能加强(装饰模式),即可以动态的对一个类添加方法(有意思不?)。
定义一个普通的接口和实现类:
package foo.bar.observed; /** * Created by xuc on 2018/1/16. */ public interface IDeclareHello { void sayBye(); }
package foo.bar.observed; import org.springframework.stereotype.Component; /** * Created by xuc on 2018/1/16. * * {@link HelloByAnnotation} 的装饰类,为其添加方法 */ @Component public class DeclareHello implements IDeclareHello{ public void sayBye(){ System.out.println("执行再见方法:Bye!"); } }
定义一个切面,再通过@DeclareParents注解(就当她是个媒婆,撮合原类和加强类,哈哈哈哈...),value为原类(男方),变量为加强接口(女方),最后就可以生娃娃了……^.^
package foo.bar.declaretion; import foo.bar.observed.DeclareHello; import foo.bar.observed.IDeclareHello; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclareParents; import org.springframework.stereotype.Component; /** * Created by xuc on 2018/1/16. * * 将{@link DeclareHello}介绍给{@link HelloIntroducer} * 这是一种装饰模式,是对原类的加强 */ @Component @Aspect public class HelloIntroducer { @DeclareParents(value = "foo.bar.observed.HelloByAnnotation", defaultImpl = DeclareHello.class) public static IDeclareHello declareHello; }
四、AOP使用--XML方式
与上面的基于注解方式无异,只是切面定义无需破坏原代码,可以再XML中实现,下面简单说明下。
1.定义一个普通的类,待监听对象
package foo.bar.observed; import org.springframework.stereotype.Component; /** * Created by xuc on 2018/1/17. */ @Component public class HelloByXml { public void sayHello(String arg) { System.out.println(arg); } }
2.再定义一个普通的类,切面类(对,切面累,不是切糕累。。),无需声明其为切面
package foo.bar.observer; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.stereotype.Component; /** * Created by xuc on 2018/1/17. * 小脑活动 */ @Component public class Cerebellum { public void think(String arg){ System.out.println("1.说话前要注意三思而后行:" + arg); } /** * 目标方法返回后执行 */ public void listen(String arg){ System.out.println("2.说完后要虚心接受长辈的教诲s"); } /** * 目标方法抛出异常后 */ public void reflection(String arg){ System.out.println("3.说错话后要反思为什么"); } /** * 目标方法前、后执行两次 */ public void doThings(ProceedingJoinPoint joinPoint, String arg) throws Throwable { System.out.println("4.准备干点其他事"); joinPoint.proceed(); System.out.println("4.其他事做完"); } }
3.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: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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:annotation-config /> <context:component-scan base-package="foo.bar"/> <!--启用aspectj自动代理--> <aop:aspectj-autoproxy /> <!-- XML方式 演示--> <!--此处仅列举一个前置通知,其他与注解形式类似--> <aop:config> <aop:aspect ref="cerebellum"> <aop:pointcut id="speak" expression="execution(* foo.bar.observed.HelloByXml.sayHello(String)) and args(arg)"/> <aop:before method="think" pointcut-ref="speak"/> </aop:aspect> </aop:config> </beans>
4.执行main测试
package foo.bar; import foo.bar.observed.HelloByAnnotation; import foo.bar.observed.HelloByXml; import foo.bar.observed.IDeclareHello; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class HelloApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); excuteByXml(context); } /** * 基于XML配置 * @param context */ private static void excuteByXml(ApplicationContext context) { HelloByXml helloByXml = context.getBean(HelloByXml.class); helloByXml.sayHello("Hello world! -- by xml"); } }
五、实例--日志
以上为Spring AOP的基本使用方法,下面举一个实际开发的例子,基于自定义注解与切面结合实现异步日志入库(实际上也是个小例子。。。)。
1.定义一个注解
package foo.bar.annotation; import java.lang.annotation.*; /** * Created by xuc on 2018/1/18. * 日志生成注解(切点) */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Inherited public @interface PrintLog { int type(); }
2.定义一个切面,声明上面的注解为其切点
package foo.bar.annotation; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; /** * Created by xuc on 2018/1/18. */ @Aspect @Component public class PrintLogOperation { @Around("within(foo.bar.observed..*) && @annotation(printLog)") public void offerMailPo(ProceedingJoinPoint jp, PrintLog printLog) throws Throwable { if (printLog.type() == 0){ System.out.println("你好啊,我是一条日志..."); } jp.proceed(); } }