Spring Framework 之AOP

问题

什么是AOP?

AOP的好处是什么?

AOP的实现方式有哪些?

Spring AOP 与 AspectJ AOP的区别?

AOP概述

​ AOP(Aspect-Oriented Programming)翻译为中文就是“面向切面编程”。从“面向过程”到“面向对象”编程,编程思想的发展永远是朝更自然、更优雅地描述世界的方向发展。“面向切面“的出现也是这一目的,它是对面向对象的补充,使编程语言能够更好地描述世界。

​ 现实编程中无法将重复出现的代码抽取至父类中。例如鉴权模块、监控模块、日志记录模块等,我们无法将公共代码块纵向抽取。那么如何横向抽取重复代码呢?AOP就能够通过横向抽取机制解决无法纵向抽取的问题,将分散在业务代码中的公共代码块抽取至一个独立的模块中。这也体现了设计模式中的“单一职责“的思想。

AOP知识

1、连接点(Joinpoint)

​ 程序执行的某个特定位置。例如类初始化前后、函数调用前后、函数抛异常后等。类或代码块具有边界性质的特定点就成为“连接点”。

2、切点(PointCut)

​ 每个类或函数都可以认为是连接点,我们如何定位我们关注的“连接点“?我们不需要为每个类或函数添加Advice,PointCut就是通过规则为我们关注的joinpoint添加Advice。

3、增强(Advice)

​ 由aspect添加到特定的Join point的代码块。

4、目标对象(Target)

​ 增强逻辑织入的目标类。

5、引介(Introduction)

​ 引介是一种特殊的增强,他为类添加一些属性和方法。

​ 例子:https://blog.csdn.net/u010599762/article/details/80182178

6、织入(Weaving)

​ 织入是将增强添加至目标类的具体连接点上的过程。

7、代理(Proxy)

​ 类被织入增强后就会产生新的结合了原类与增强的代理类。在Spring AOP中有两种代理,分别是JDK动态代理和CGLib动态代理。

8、切面(Aspect)

​ 切面由切点和增强组成,包括增强的横切逻辑和连接点。Spring AOP负责将切面中的增强逻辑织入指定的连接点中。

代理

静态代理

代理模式

​ 代理模式提供对目标对象进行访问方式,即通过代理对象访问目标对象。可以在目标对象的基础上增强,提供个性功能,达到扩招目标对象功能的作用。

接口

public interface Subject {
void request();
}

具体实现

public class RealSubject implements Subject {
public void request() {
//业务逻辑
}
}

代理类

public class Proxy implements Subject {

    //要代理的实现类
private Subject subject = null; public Proxy() {
this.subject = new Proxy();
} //通过构造函数传递代理者
public Proxy(Object... objects) { } //实现接口定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
} //预处理
public void before() { } //后处理
public void after() { }
}

动态代理

JDK动态代理

​ JDK动态代理设计到java.lang.relect包中的两个类:Proxy和InvocationHandler,InvocationHandler可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑与业务逻辑编织到一起。Proxy利用InvocationHandler动态创建某一符合该接口的实例,生成目标类的代理对象。

public class Monitor {

    public static void begin(){
System.out.println("before");
} public static void end(){
System.out.println("after");
}
}
public interface CouponService {

    void getCoupon();
}
public class CouponServiceImpl implements CouponService {

    public void getCoupon() {

        //Monitor.begin();
try {
System.out.println("业务代码");
} catch (Exception e) {
throw new RuntimeException();
}
//Monitor.end();
}
}
public class PerformanceHandler implements InvocationHandler {

    //被代理对象
private Object target; public PerformanceHandler(Object target) {
this.target = target;
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Monitor.begin();
Object object = method.invoke(target, args);
Monitor.end();
return object;
}
}
 public class Client {

    public static void main(String[] args) {

        //被代理对象
CouponService target = new CouponServiceImpl(); //让PerformanceHandler将监视横切逻辑编织到CouponService中
PerformanceHandler performanceHandler = new PerformanceHandler(target); //通过Proxy的newProxyInstace()方法,为编织了业务逻辑与监控逻辑的handler创建一个符合CouponService接口的代理实现
CouponService proxy = (CouponService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),performanceHandler); proxy.getCoupon();
}
}
CGLIB动态代理

​ JDK创建代理只能为接口创建代理,实际开发中我们很难保证每个类都有其对应的接口,对于没有通过接口定义业务方法的类,JDK已经没法对其进行代理,这就出现了Cglib,通过字节码技术,为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并织入横切逻辑。

public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
} @Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
//通过代理类调用父类中的方法
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("after");
return result;
}
}
public class Client {

    public static void main(String[] args) {
CglibProxy proxy = new CglibProxy(); //通过冬天生成子类的方式创建代理类
CouponServiceImpl couponService = (CouponServiceImpl) proxy.getProxy(CouponServiceImpl.class); couponService.getCoupon();
}
}

静态代理与动态代理区别

(1)静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件

(2)动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中

JDK动态代理 与CGLIB代理区别

​ Cglib所创建的动态代理,性能要比jdk创建的动态代理高。但对用Cglib创建代理的时间,JDK动态代理显然要快很多。对于无需单例的代理对象或实例池可以使用CGLib来创建代理(无需频繁创建),反之使用JDK动态代理。

@AspectJ

​ AspectJ是语言级使用Java注解来AOP实现的一种方式,扩招Java语言,定义AOP语法,能够在编译期间提供横切代码织入。

切面定义

Java Configuration方式配置

@Configuration
@EnableAspectJAutoProxy
public class AOPConfig {
}

xml方式配置

<aop:aspectj-autoproxy/>

切面声明

@Component
@Aspect
public class ControllerIntercept {
}

通过@Aspect将ControllerIntercept标识成一个切面,通过@Component将ControllerIntercept标识为一个Bean,Spring框架会收集@Aspect标注的Bean,并将其添加至Spring AOP中。

切点声明

@Pointcut("execution(public * com.ljw.discern_spider.controller.DiscernController.discern(..))")
public void discernWeb() {
}

一个 pointcut 的声明由两部分组成:

  • 一个方法签名, 包括方法名和相关参数
  • 一个 pointcut 表达式, 用来指定哪些方法执行是我们感兴趣的(即因此可以织入 advice).

切点函数

​ AspectJ5.0的切点表达式由关键字和操作参数组成,如 "execution( greetTo(..))" 的切点表达式, execution 就是关键字, 而圆括号里的 greetTo(..) 就是操作参数。

函数 入参 说明
execution() 方法匹配模式串 满足方法匹配模式串所有目标类方法连接点。例如:@Pointcut("execution(public * com.ljw.controller.DiscernController.discern(..))") ,标识目标类中的discern方法
@annatation 方法注解类名 表示标注特定注解的目标类方法连接点。例如:@Pointcut("@annotation(com.ljw.ABC)"),表示标注@ABC注解的目标类连接点
within() 类名匹配串 表示特定路径下的所有连接点。例如:within(com.ljw.*),表示com.ljw路径下的所有连接点。within(com.ljw.*Service),表示com.ljw路径下以Service结尾的类中的所有连接点
args() 类名 匹配参数满足要求的方法。例如@Before(value = "aspectMethod() && args(name)"),只有一个参数且为name的方法;adgs(com.ljw.ABC),表示所有且仅有一个入参类型与ABC向匹配的方法。
target() 类名 目标类按类型匹配指定类。目标类的所有连接点匹配这一切点。例如:target(com.ljw.ABC)

常见的切点表达式

按方法签名匹配

@Pointcut("execution(public * com.ljw.controller.DiscernController.discern(..))")

按类匹配

@Pointcut("within(com.ljw.*)")
@Pointcut("within(com.ljw..*)")
//匹配实现接口的所有类中的实现的方法
@Pointcut("within(ABCService+)")

按Bean名称匹配

@Pointcut("bean(*Service)")

逻辑运算符运用

可以运用 || 、&& 、!等逻辑运算符

@Pointcut("bean(*Service || *ServiceImpl)")
@Pointcut("bean(*Service) && within(com.ljw.service.*)")

通配符

*匹配任意字符。只能匹配上下文中的一个元素。

..:匹配任意字符。可以匹配上下文中多个元素。标识类时需要与*结合使用,标识参数时可以单独使用。

+:表示按类型匹配指定类的所有类,必须跟在类名后面。继承或实现指定类的所有类。

增强声明

@Around("discernWeb()")
public Object around(ProceedingJoinPoint joinPoint) {
long start = System.currentTimeMillis();
try {
before(joinPoint);
return joinPoint.proceed(joinPoint.getArgs());
} catch (Throwable t) {
return Result.createFalseRet().withErrMsg("around exception");
}
}

可以从ProceedingJoinPoint中获取参数,对参数进行操作。

@Before:前置增强。

@After:后置增强。

@Around:环绕增强。可以在方法前后进行不同的操作。

参考

[1]《精通Spring4.x企业应用开发实战》

[2]https://segmentfault.com/a/1190000011291179

[3]https://segmentfault.com/a/1190000007469968

Spring Framework 之AOP的更多相关文章

  1. 框架应用:Spring framework (二) - AOP技术

    基础概念 线程中的方法栈 java程序虚拟机启动时会载入程序码,虚拟机会为每一条正在运行的线程生成一个方法调用栈,线程以方法运行为执行单位. AOP概念以及目标 AOP是面向切面编程,其实就是在不修改 ...

  2. Hello Spring Framework——面向切面编程(AOP)

    本文主要参考了Spring官方文档第10章以及第11章和第40章的部分内容.如果要我总结Spring AOP的作用,不妨借鉴文档里的一段话:One of the key components of S ...

  3. 转-Spring Framework中的AOP之around通知

    Spring Framework中的AOP之around通知 http://blog.csdn.net/xiaoliang_xie/article/details/7049183 标签: spring ...

  4. 【Spring Framework】Spring入门教程(六)Spring AOP使用

    Spring的AOP 动态代理模式的缺陷是: 实现类必须要实现接口 -JDK动态代理 无法通过规则制定拦截无需功能增强的方法. Spring-AOP主要弥补了第二个不足,通过规则设置来拦截方法,并对方 ...

  5. 【Spring Framework】Spring入门教程(五)AOP思想和动态代理

    本文主要讲解内容如下: Spring的核心之一 - AOP思想 (1) 代理模式- 动态代理 ① JDK的动态代理 (Java官方) ② CGLIB 第三方代理 AOP概述 什么是AOP(面向切面编程 ...

  6. 浅谈对Spring Framework的认识

    Spring Framework,作为一个应用框架,官方的介绍如下: The Spring Framework provides a comprehensive programming and con ...

  7. 手动创建Spring项目 Spring framework

    之前学习框架一直是看的视频教程,并且在都配套有项目源码,跟着视频敲代码总是很简单,现在想深入了解,自己从官网下载文件手动搭建,就遇到了很多问题记载如下. 首先熟悉一下spring的官方网站:http: ...

  8. Spring Framework------>version4.3.5.RELAESE----->Reference Documentation学习心得----->关于spring framework中的beans

    Spring framework中的beans 1.概述 bean其实就是各个类实例化后的对象,即objects spring framework的IOC容器所管理的基本单元就是bean spring ...

  9. Spring Framework------>version4.3.5.RELAESE----->Reference Documentation学习心得----->使用spring framework的IoC容器功能----->方法一:使用XML文件定义beans之间的依赖注入关系

    XML-based configuration metadata(使用XML文件定义beans之间的依赖注入关系) 第一部分 编程思路概述 step1,在XML文件中定义各个bean之间的依赖关系. ...

随机推荐

  1. python3(二十五) getClassInfo

    """ """ __author__ = 'shaozhiqi' # 如何知道这个对象是什么类型,使用type() print(type(1 ...

  2. hadoop(四)centos7克隆|静态ip|机器名|映射关系|别名配置(完全分布式准备一)|6

    hadoop完全分布式准备工作 克隆默认基础虚拟机三台102/103/104目标:在win10主机上能连上这三台机器,三台机器之间可以互相ping通,用机器名也可ping通.基础虚拟机:创建了文件op ...

  3. docker-compose 基于Dockerfile 安装并启动redis容器的血案

    前言 为了实现"一键部署"的目的,我采用Dockerfile 和 docker-compose来实现自己的目的.这个过程中,我怎么也无法启动自己的redis-server服务. 目 ...

  4. 数据结构和算法(Golang实现)(8.1)基础知识-前言

    基础知识 学习数据结构和算法.我们要知道一些基础的知识. 一.什么是算法 算法(英文algorithm)这个词在中文里面博大精深,表示算账的方法,也可以表示运筹帷幄的计谋等.在计算机科技里,它表示什么 ...

  5. centos7用户管理及root忘记密码恢复

    查看用户相关命令:#id 用户和组的信息#whoami #查看当前有效用户名#who #显示目前登入系统的用户信息.#w # w 命令用于显示已经登陆系统的用户列表#users #用于显示当前登录系统 ...

  6. X - Skyscrapers (hard version) CodeForces - 1313C2

    题目大意:n个高楼,每个楼最高为mi,要求,第i个楼左边和右边不能有同时比它高的楼.让你求最在n个楼总和最高的情况下,每个楼的高度. 题解:用单调栈来做,n个楼的高度要么是单调递减,要么是单调递增,要 ...

  7. AI vs PS 矢量 VS 位图

    矢量图 AI最大可以放大64000%.不会失真,依然很清晰.原理是不同的点以及点与点之间的路径构成的,不论放大的多大,点在路径在,就可以精确的计算出它的区域.AI中无法直接编辑位图. 位图 代表PS, ...

  8. [javascript]各种页面定时跳转(倒计时跳转)代码总结

    (1)使用setTimeout函数实现定时跳转(如下代码要写在body区域内) <script type="text/javascript"> //3秒钟之后跳转到指定 ...

  9. Redis的三大问题

    一般我们对缓存读操作的时候有这么一个固定的套路: 如果我们的数据在缓存里边有,那么就直接取缓存的. 如果缓存里没有我们想要的数据,我们会先去查询数据库,然后将数据库查出来的数据写到缓存中. 最后将数据 ...

  10. 图数据库的内部结构 (NEO4j)

    What “Graph First” Means for Native Graph Technology Neo4j是一个具有原生处理(native processing)功能和原生图存储(nativ ...