spring使用之旅(二) ---- AOP的使用

时间:2021-01-20 03:55:19
  1. 什么是AOP?
  2. AOP基本概念
  3. AOP使用--注解方式
  4. AOP使用--XML方式
  5. 实例--日志

  

  写在最前面的(源码地址):

    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();
}
}

 

    环境: IDEA、Spring4.0
    参考资料: 《spring实战》