Spring AOP简介

Spring AOP 简介

1 Spring AOP

1.1 AOP简介

AOP,面向切面编程是对OOP的补充。在日常的开发中,我们所关心的业务流程可以称之为核心关注点,而一些穿插在业务逻辑中的公共的与业务无关的处理逻辑称之横切关注点。其实,就算是与业务有关的公共处理,做成切面也是合理的。Spring的Bean的概念,IoC(DI)的概念是AOP的基础,本质上切面也是Spring的一种bean,在此我会简述下Spring Bean和IoC的基础知识。
1) Spring IoC和DI简述:Bean,IoC(DI),Ioc容器,创建Bean和获取Bean的方式
①Bean是Spring IoC容器管理的对象
②IoC(DI):对象(依赖或耦合)关系由Spring控制(管理)。容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所谓“反转”。IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。理解:化主动为被动。好处:解耦
DI:与IoC是同一概念的不同角度描述。被注入对象依赖IoC容器配置依赖对象
③IoC容器:BeanFactory和ApplicationContext
④bean配置:注册到Spring容器,Xml和注解。基于组件扫描的注解有@Component、@Service、@Repository、@Controller。
⑤注入:构造器注入,setter注入。
2)AOP
OOP:面向对象编程。
AOP:面向切面编程。切面:横切关注点。业务流程:核心关注点。
切面:与业务无关,被业务所共同调用的逻辑处理。
AOP和OOP的一个简单对比如下图。 ![](https://i.imgur.com/QthYHlM.jpg)
实现方式:xml和注解
实例:事务管理,日志,权限认证 看下图日志的处理方式,做成切面不仅仅简化编码,还能与业务逻辑解耦,在业务逻辑完全不知情的情况下实现日志功能。 ![](https://i.imgur.com/JE8Yby5.jpg) ###1.2 AOP概念
Spring AOP:对某些类的某些方法做一些统一的处理逻辑
1.横切关注点:对哪些方法拦截,拦截后怎么处理
2.切面(aspect):横切关注点的抽象
3.**链接点(joinpoint):程序执行的一个点,如方法的执行和异常的处理。Spring AOP中,连接点始终 表示方法执行**。
4.**切入点(pointcut):匹配链接点的定义**
		@Pointcut("execution(* oneFunction(..))")// the pointcut expression
		private void oneAdvice() {}// the pointcut signature


5.通知(advice):拦截到连接点之后要执行的代码
这里要着重理解链接点,切入点和通知的概念。链接点指的是一个方法(对Spring AOP),切入点是匹配链接的定义,好比链接点是集合中的元素,而切入点是这个集合的定义。通知,有时称增强处理,就是拦截到方法之后的额外处理。

6.AOP代理(AOP proxy):AOP框架代理生成的对象用于对切面处理。

7.目标对象:代理的目标对象

8.织入(weave):将切面应用到目标对象并导致代理对象创建的过程

9.通知(advice)分类:

前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

异常通知(After throwing advice):在方法抛出异常退出时执行的通知。

最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

环绕通知(Around Advice):包围一个连接点的通知,如方法调用。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

1.3 AOP代理


理解AOP代理

1)Java动态代理和CGLIB

Java动态代理:动态代理为一接口在运行期动态生成实现对象,生成的对象中实现接口的方法时可以添加增强代码,从而实现AOP。只能针对接口进行代理,通过反射实现的,需要考虑反射开销。

2)CGLIB:动态字节码生成技术是指在运行时动态生成指定类的一个子类对象,并覆盖其中特定方法,覆盖方法时可以添加增强代码,从而实现AOP。

AOP代理


Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理

1.(代理接口)默认使用java动态代理来创建AOP代理

2.(代理类不是代理接口)显式强制使用CGLIB代理

开发流程:

1.定义普通业务组件

2.定义切入点,一个切入点可能横切多个业务组件

3.定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

代理对象的方法=增强处理+被代理对象的方法。

如图示


使用CGLIB代理需要额外配置如下。

	 <aop:aspectj-autoproxy proxy-target-class="true"/>
##2 AOP实现 ###2.1 XML格式
1.context配置与依赖:1),aopalliance.jar;2),spectjweaver.jar
xml配置bean的id,class等
2.定义业务接口类与业务实现类
这里定义一个业务接口及其实现类。 业务接口 public interface IMyFunction { void printHello(); void printNero(); }
实现
public class MyFunctionImpl1 implements IMyFunction {
@Override
public void printHello() {
    System.out.println("MyFunctionImpl1:Hello");
}

@Override
public void printNero() {
    System.out.println("MyFunctionImpl1:Nero");
}

}


3.定义切面
这里就是打印时间
public class TimeAspect {

public  void  printTimeOne(){
    System.out.println("time one is "+System.currentTimeMillis());
}
public  void  printTimeTwo(){
    System.out.println("time two is "+System.currentTimeMillis());
}

}

3.定义切入点,定义增强处理(通知)
这是基于xml格式。aop:aspect元素将TimeSpect类注册为一个切面,功能是在匹配的方法前后打印时间戳。注意pointcut的表达式expression,表示匹配任意返回类型,接口为com.zafkile.Service.IMyFunction的任意无参方法,这里就是匹配printHello()和printNero()方法。

<bean id="myFunctionImpl1" class="com.zafkiel.Service.impl.MyFunctionImpl1"/>
    <bean id="timeAspect" class="com.zafkiel.Aspect.TimeAspect"/>
    <aop:config>
            <aop:aspect id="showTime" ref="timeAspect">
                    <aop:pointcut  id="weaveOnSomeMethod" expression=" execution(* com.zafkiel.Service.IMyFunction.*())"/>
                    <aop:before method="printTimeOne" pointcut-ref="weaveOnSomeMethod"/>
                    <aop:after method="printTimeTwo" pointcut-ref="weaveOnSomeMethod"/>
            </aop:aspect>
    </aop:config>


4.测试
写个简单测试类测试一下。

public class Main {
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    IMyFunction myFunction1 = (MyFunctionImpl1) context.getBean("myFunctionImpl1");
    myFunction1.printHello();
    System.out.println("----------------------");
    myFunction1.printNero();
    System.out.println("----------------------");
}
}
输出结果:

time one is 1538914674033
MyFunctionImpl1:Hello
time two is 1538914674052
----------------------
time one is 1538914674052
MyFunctionImpl1:Nero
time two is 1538914674052
----------------------

2.2 注解格式


1.导入依赖包,配置CGLIB代理

    <context:component-scan  base-package="com.zafkiel"/>
  	 <aop:aspectj-autoproxy  proxy-target-class="true"/>


2.定义业务接口和实现业务类

public interface IAroundService {
AroundServiceRspVO aroundService(AroundServiceReqVO reqVO, String fruit, int count);
}

实现类

@Service
public class AroundServiceImpl implements IAroundService {

@Override
public AroundServiceRspVO aroundService(AroundServiceReqVO reqVO, String fruit, int count) {
     AroundServiceRspVO rspVO=new AroundServiceRspVO();
     rspVO.setAlterNero("alterNero");
     rspVO.setAlterSaber("alterSaber");
     System.out.println("args[1] fruit :"+fruit);
      if("homula".equals(fruit)){
          rspVO.setAlterNero("alterHomula");
          rspVO.setAlterSaber("alterHomula");
      }
     return  rspVO;
}
@Override
public String  toString(){
    return (getClass().getName()+"@"+hashCode());
}
}


3.定义切面

4.定义切入点,定义通知
这里3,4步合在一起。简单说明下,pointcut是切入点(定义),定义了一个环绕通知aroudAdvice(XXX),主要观察目标方法执行前后的变化,以及改变目标方法的入参。

@Component
@Aspect
public class AroundAspect {

@Pointcut("execution(* com.zafkiel.Service.IAroundService.*(*,String,*))")
public void pointcut() {
}

@Around("pointcut()")
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("---------------------");
    System.out.println("执行目标方法前");
    Object proxy = AopContext.currentProxy();
    System.out.println("proxy:" + proxy.getClass().getName());
    System.out.println("proxy:" + proxy);
    Object target= AopTargetUtils.getCglibProxyTargetObject(proxy);
    System.out.println("target:"+target.getClass().getName());
    System.out.println("target:"+target);
    System.out.println(proxy==target);
    System.out.println(proxy.equals(target));
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Object returnType = methodSignature.getReturnType();
    System.out.println("methodSignature:" + methodSignature.toString());
    System.out.println("目标对象:" + joinPoint.getTarget());
    System.out.println("目标方法:" + methodSignature.getDeclaringTypeName() + "." + methodSignature.getName());
    Object[] args = joinPoint.getArgs();
    if (null != args && args.length > 0) {
        if (args[0] instanceof AroundServiceReqVO) {
            AroundServiceReqVO arg0 = (AroundServiceReqVO) args[0];
            System.out.println("入参0:" + arg0);
            System.out.println("arg0.nero:" + arg0.getNero());
            System.out.println("arg0.saber:" + arg0.getSaber());
        }
        if (args[1] instanceof String) {
            System.out.println("入参1:" + (String) args[1]);
            System.out.println("我将会改变入参1,改变为:" + "homula");
            args[1] = "homula";
        }
        if (args[2] instanceof Integer) {
            System.out.println("入参2:" + (Integer) args[2]);
        }
    }
    System.out.println("---------------------");

    System.out.println("开始执行目标方法");
    Object returnValue = joinPoint.proceed(args);
    System.out.println("执行目标方法结束");
    System.out.println("---------------------");

    System.out.println("改变参数1后执行返回的结果为:" + returnValue.toString());
    AroundServiceRspVO returnObject = (AroundServiceRspVO) returnValue;
    System.out.println("returnObject.alterNero:" + returnObject.getAlterNero());
    System.out.println("returnObject.alterSaber:" + returnObject.getAlterSaber());
    System.out.println("---------------------");
}
}


5.测试

写个测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext.xml"})
public class AroundServiceTest /*extends BaseJunit4Test */{
@Autowired
private IAroundService aroundService;
@Test
public  void  test()  throws  Exception{
    AroundServiceReqVO reqVO=new AroundServiceReqVO();
    reqVO.setNero("Nero");
    reqVO.setSaber("Saber");
    aroundService.aroundService(reqVO,"apple",1);
}

}

测试结果:

---------------------
执行目标方法前
proxy:com.zafkiel.Service.impl.AroundServiceImpl$$EnhancerBySpringCGLIB$$b85f2d1b
proxy:com.zafkiel.Service.impl.AroundServiceImpl@800493254
target:com.zafkiel.Service.impl.AroundServiceImpl
target:com.zafkiel.Service.impl.AroundServiceImpl@800493254
false
false
methodSignature:AroundServiceRspVO com.zafkiel.Service.impl.AroundServiceImpl.aroundService(AroundServiceReqVO,String,int)
目标对象:com.zafkiel.Service.impl.AroundServiceImpl@800493254
目标方法:com.zafkiel.Service.impl.AroundServiceImpl.aroundService
入参0:com.zafkiel.POJO.AroundServiceReqVO@72889280
arg0.nero:Nero
arg0.saber:Saber
入参1:apple
我将会改变入参1,改变为:homula
入参2:1
---------------------
开始执行目标方法
args[1] fruit :homula
执行目标方法结束
---------------------
改变参数1后执行返回的结果为:com.zafkiel.POJO.AroundServiceRspVO@606fc505
returnObject.alterNero:alterHomula
returnObject.alterSaber:alterHomula

这里就能比较清楚的看到目标方法执行前后的变化。
XML格式和注解格式对比
XML格式是比较繁琐的,但是对于第三方API、无法获取源码的类,只能通过XML格式来实现AOP,此外XML格式没有代码侵入。对于自己开发和维护的类,还是推荐注解格式,毕竟实现简单。

3 Spring AOP属性详解


1.核心概念(来自Spring文档)
这里可以看下官方对这些核心概念的定义。

切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。

连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。

通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。

切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。

目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。


2.五种通知

环绕通知(Around Advice)

前置通知(Before advice)

后置通知(After returning advice)

异常通知(After throwing advice)

最终通知(After (finally) advice)


3.切入点表达式

切入点表达式,也就是组成@Pointcut注解的值。

1)execution:

		execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)


2)@annotation - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中连接点的主题持有指定的注解

3)其他

param-pattern:()匹配了一个不接受任何参数的方法, 而(..)匹配了一个接受任意数量参数的方法(零或者更多)。 模式()匹配了一个接受一个任何类型的参数的方法。 模式(,String)匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型



4.通知顺序

同一个切面的通知按照声明顺序。

多个切面的通知想要在同一连接点运行。切面类中实现org.springframework.core.Ordered 接口或者用Order注解



5.理解AOP代理

①代理:代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法、实际执行的是被代理类的方法。


②JDK动态代理 与CGLIB:

JDK动态代理:生成新的实现类,只能针对实现接口的类。

CGLIB:CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。

CGLib不能对声明为final的方法进行代理

③效率:

测试:jdk10,数量不大,cglib效率高一点;jdk10,数量大,jdk代理效率高大概50%


6.推荐阅读:
Spring方面有很多书,但个人比较推荐的就是spring 的官方文档。

https://docs.spring.io/spring/docs/

后话:
希望这篇博客能对各位博友对Spring AOP有一个比较清晰的认识,文中的代码也是可以直接执行的。然后,这是我第一次发表博客,希望同样热爱技术的你们会喜欢!

posted @ 2018-10-07 20:39  Warspite  阅读(235)  评论(0编辑  收藏  举报