部分概念引子文章:https://blog.csdn.net/changudeng1992/article/details/80625134
在理解AOP(面向切面)前,我们先来回顾下日常我们开发过程中经常接触的OOP(面向对象编程)。
百度百科OOP:面向对象程序设计(Object Oriented Programming)作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。
个人理解的OOP:OOP的核心即万物皆对象,其主要关注将功能需求划分设计为多个不同的且相对独立的封装良好的类,并让它们拥有属于自己的属性标签和事件行为,可以依靠继承、多态、聚合等来定义彼此之间的关联关系。
理解了OOP(面向对象编程),我们再来看AOP(面向切面编程)。需要我们特别注意的一点是,OOP和AOP不存在竞争关系,两者同作为程序设计的一种思想,很好的进行了互补。我们首先看百度百科对于AOP的定义:
百度百科AOP:在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
个人理解AOP:是一种设计模式,AOP与传统的OOP(面向对象编程)相比,OOP的代码逻辑是至上而下的,而这些至上而下的过程中会产生一些横切性问题,即很多和我们的主业务逻辑相关性不大的代码,其会散落在各个业务功能代码种,以致造成代码冗余、难以维护。AOP的编程设计思想就是要把业务逻辑和横切性的问题进行分离,从而达到解耦的目的,减少维护成本和代码冗余,提高代码的复用性和开发效率。
通过文字描述可能对AOP的概念还不清晰透彻,下面我们通过图1来更直观地理解AOP的设计思想(图片来源于百度):
图1
图1中的“软件核心业务模块”即我们日常程序开发过程中的业务逻辑代码,而“权限模块”、“日志模块”、“事务模块”通常是无关业务的功能性代码,它们对应的就是我们前面所说的横切性问题。
AOP应用于各种场景,常见如下:
- 权限模块
- 日志记录
- 事务管理
- 异常捕获处理
- 性能优化和监控
- 效率检查
AOP术语 :
- Aspect(切面)
- Join Point(连接点):
- PointCut(切点):
- Advice(通知):
- Weaving(织入)
- Introduction(引入)
- Target(目标)
- AOP Proxy(代理对象)
- AspectJ
1、Aspect(切面)
切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
2、Join Point(连接点)
就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。
3、PointCut(切点)
上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
4、Advice(通知)
就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。
5、Weaving(织入)
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。
6、Introduction(引入)
允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗
7、Target(目标)
引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
8、Proxy(代理)
怎么实现整套aop机制的,都是通过代理,这个一会给细说。
9、AspectJ
AOP框架,Spring借助了AspectJ的语法,用了其注解。
下面通过简单的代码示例来理解下:
package com.tyyd.dao;
import org.springframework.stereotype.Repository;
@Repository
public class IndexDao {
public void query(String s) {
System.out.println("Query");
}
}
package com.tyyd.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @EnableAspectJAutoProxy 此注解表示让工程项目支持AspectJ语法
* @EnableAspectJAutoProxy(proxyTargetClass = false)或@EnableAspectJAutoProxy表示使用JDK动态代理
* @EnableAspectJAutoProxy(proxyTargetClass = true)表示使用CGLIB
*/
@Configuration
@ComponentScan("com")
@EnableAspectJAutoProxy
public class AppConfig {
}
package com.tyyd.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 如果同时定义了within、execution、args,那么spring会根据三者的范围大小,先后执行args、execution、within
*/
@Component
@Aspect
public class ServiceAop {
/**
* 本方法为定义一个切点
* 切点的声明:com.tyyd.dao包下的任意返回值,任意类,任意名称,任意参数的public方法(public不写的话表示任意方法)
* 匹配包含一个String参数的方法可以写成@Pointcut("execution(public * com.tyyd.dao.*.*(java.lang.String,..))")
* 等同于@Pointcut("execution(public * com.tyyd.dao.*.*(..,java.lang.String))")
* 等同于等同于@Pointcut("execution(public * com.tyyd.dao.*.*(java.lang.String))")
* 如果方法中包含多个参数,则要注意参数顺序,比如第一个参数是String,第二个参数是Integer。
* 那么要写成@Pointcut("execution(public * com.tyyd.dao.*.*(java.lang.String,java.lang.Integer))")
*
* 注:方法名取什么无所谓
*/
@Pointcut("execution(public * com.tyyd.dao.*.*(java.lang.String,..))")
public void ponitCutExecution() {
}
/**
* execution与within的主要区别
* execution可以定义的范围可以延申到包名、方法修饰符、方法名、方法返回值、方法参数等等任意细小粒度。
* within可以定义的最小粒度是类
*/
@Pointcut("within(com.tyyd.dao.*)")
public void pointCutWithin() {
}
/**
* 匹配包含一个String参数的方法
*/
@Pointcut("args(java.lang.String)")
public void pointCutArgs() {
}
/**
* 匹配包含Entity注解的对象
*/
@Pointcut("@annotation(com.tyyd.anno.Entity)")
public void pointCutAnnotation() {
}
/**
* 匹配当前生成的代理对象=IndexDao
*/
@Pointcut("this(com.tyyd.dao.IndexDao)")
public void pointCutThis() {
}
/**
* 匹配目标对象=IndexDao
*/
@Pointcut("target(com.tyyd.dao.IndexDao)")
public void pointCutTarget() {
}
/**
* 通知即我们前面指的权限、日志、事务等横切问题——通用的功能代码
* 通知 并指定通知的切点
*/
@Before("ponitCutExecution()")
public void beforeExecution() {
System.out.println("beforeExecution");
}
@Before("pointCutWithin()")
public void beforeWithin() {
System.out.println("beforeWithin");
}
@Before("pointCutArgs()")
public void beforeCutArgs() {
System.out.println("beforeArgs");
}
/**
* spring还支持表达式,比如如下所示&&!的关系符,即包含pointCutWithin()且不包含pointCutArgs()
*/
@Before("pointCutWithin()&&!pointCutArgs()")
public void beforeCutWithinAndArgs() {
System.out.println("beforeArgs");
}
@Before("pointCutAnnotation()")
public void beforeCutAnnotation() {
System.out.println("beforeAnnotation");
}
@Before("pointCutTarget()")
public void beforeCutTarget() {
System.out.println("beforeTarget");
}
}
import com.tyyd.config.AppConfig;
import com.tyyd.dao.IndexDao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
IndexDao indexDao = annotationConfigApplicationContext.getBean(IndexDao.class);
indexDao.query("11");
indexDao.delete();
}
}
package com.tyyd.anno;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
}