AOP 工作流程 (了解)
- Spring 容器启动
- 读取所有切面配置中的切入点(读取的是全部配置,强调切入点,是因为现在说明的就 是 AOP 相关工作流程)
- 初始化 bean,判定 bean 对应的类中的方法是否匹配到任意切入点
- 匹配失败,不创建代理对象,仅创建目标对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取 bean 执行方法
- 获取 bean,调用方法并执行,完成操作
- 获取的 bean 是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容, 完成操作
- 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直 接完成最终工作的
- 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
切入点表达式
切入点就是要进行增强的方法,切入点表达式就是要进行增强的方法的描述方式
表达式组成
- 切入点表达式标准格式:
- 动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数表)异常)
- 动作关键字:描述切入点的行为动作,例如 execution 表示执行到指定切入点
- 访问修饰符:public,private 等,可以省略
- 返回值:具体的返回值类型
- 包名
- 类/接口名
- 方法名
- 参数表,只写类型,不写参数名
- 异常:方法定义中抛出指定异常,可以省略
综上所述,一个完整的表达式如下
- 动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数表)异常)
执行 com.tianshi.service 包中,StudentService 接口中的没有返回值的,命名为 saveStudent,唯一参数类型是 com.tianshi.bean.Student 的方法
java
execution( void com.tianshi.service.StudentService.saveStudent(com.tianshi.bean.Student) )
执行 com.tianshi.service.impl 包中,StudentServiceImpl 类中,返回值类型是 java.util.List,命名为 getAllStudents 的无参数方法
java
execution( java.util.List com.tianshi.service.impl.StudentServiceImpl.getAllStudents() )
表达式通配符
- 看到上面的例子,发现编写切入点表达式的时候是非常的麻烦,Spring 可以使用通配符 来描述切入点表达式,从而实现快速描述
通配符 *
- 代表任意单个独立的任意符号。它可以出现在类名、方法名、返回值或参数列表中 的任意位置,用来匹配任意的字符。但是,当它用于方法参数时,表示任意类型的一个参数
- 使用场景
- 类名或接口名:可以用来匹配任意的类或接口名称的全部或部分字符
java
// 可以匹配所有以 Service 结尾的类或接口
execution( void com.tianshi.service.*Service.saveStudent(com.tianshi.bean.Student) )
- 方法名:用于匹配方法名中的任意个字符
java
// 匹配接口 StudentService 中的任意方法
execution( void com.tianshi.service.StudentService.*(com.tianshi.bean.Student) )
// 匹配接口 StudentService 中所有以 get 开头的方法
execution( void com.tianshi.service.StudentService.get*(com.tianshi.bean.Student) )
- 返回值:用于匹配任意类型返回值的方法
java
execution( * com.tianshi.service.StudentService.*(com.tianshi.bean.Student) )
- 参数列表:单独使用时,如 (*),表示该方法可以接受任意类型的一个参数;但在实际应用中,通 常与其它通配符结合使用来表示任意数量的参数
java
// 匹配接口 StudentService 中的仅有一个任意类型参数的任意方法
execution( * com.tianshi.service.StudentService.*( * ) )
通配符...
- 连续的两个点,代表任意数量(包括零)的任意符号,可以独立出现,常用于匹配 包名的层级或者方法参数列表
- 使用场景:
- 包名:在匹配包路径时,... 可以用来表示当前包及其所有子包
java
// 匹配 com.tianshi 包及其所有子孙包中所有以 Service 结尾的接口或类中,仅有唯一参数 的任意方法
execution( * com.tianshi..*Service.*(*) )
2.参数列表:在方法签名中,(...) 表示任意数量和类型的参数,这比单个 * 更加灵活,可以匹配无 参数方法、单一参数方法或多个参数的方法
java
// 匹配 com.tianshi 包及其所有子孙包中所有以 Service 结尾的接口或类中的任意方法
execution( * com.tianshi..*Service.*(..) )
表达式书写技巧
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类
- 访问控制修饰符针对接口开发均采用 public 描述(可省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
- 包名书写尽量不使用...匹配,效率过低,常用*做单个包描述匹配,或精准匹配。 方法参数表都通过...配置
- 接口名 /类名书写名称与模块相关的采用*匹配,例如 UserService 书写成*Service, 绑定业务层接口名 接口名规范 : XxxDao XxxService IXxxDao IXxxService 实现类名规范: XxxDaoImpl XxxServiceImpl XxxDao XxxService
- 法名书写以动词进行精准匹配,名词采用匹配,例如 getById 书写成 getBy ,insertStudent 写成 insert*,selectAll 书写成 selectAll
- DAO 方法名: insertXxx() updateXxx() deleteXxxx() selectXxx()
- Service 方法名: addXxx() saveXxx() modifyXxx() dropXxx() removeXxx() getXxx() findXxx()
- 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常 作为匹配规则,省略异常的配置