约定编程与Sping AOP
一、约定编程
Spring AOP是一种约定流程的编程,咱们可以先通过动态代理模式的实现来理解Spring AOP的概念。
代理的逻辑很简单,例如,当你需要采访一名儿童时,首先需要经过他父母的同意,在一些问题上父母也许会替他回答,而对于另一些问题,也许父母觉得不太适合这个小孩会拒绝掉,显然这时父母就是这名儿童的代理(proxy)了。通过代理可以增强或者控制对儿童这个真实对象(target)的访问。

1. 首先实现拦截器接口Interceptor(自己定义的接口)
下面代码均使用spring boot 2.6.2
package com.springboot.chapter4.intercept; import java.lang.reflect.InvocationTargetException;
import com.springboot.chapter4.invoke.Invocation; public class MyInterceptor implements Interceptor { @Override
public boolean before() {
System.out.println("before ......");
return true;
} @Override
public boolean useAround() {
return true;
} @Override
public void after() {
System.out.println("after ......");
} @Override
public Object around(Invocation invocation)
throws InvocationTargetException, IllegalAccessException
{
System.out.println("around before ......");
Object obj = invocation.proceed();
System.out.println("around after ......");
return obj;
} @Override
public void afterReturning() {
System.out.println("afterReturning......"); } @Override
public void afterThrowing() {
System.out.println("afterThrowing......");
} }

2 创建代理对象
在Java的JDK中,提供了类Proxy的静态方法——newProxyInstance,给予我们来生成一个代理对象(proxy):
public static Object newProxyInstance(ClassLoader classLoader, Class<?>[] interfaces,
InvocationHandler invocationHandler) throws IllegalArgumentException
它有3个参数:
•classLoader——类加载器;(被代理的类)
•interfaces——绑定的接口,也就是把代理对象绑定到哪些接口下,可以是多个;(被代理的类实现的接口列表)
•invocationHandler ——绑定代理对象逻辑实现。
这里的invocationHandler是一个接口InvocationHandler对象,它定义了一个invoke方法,这个方法就是实现代理对象的约定流程
package com.springboot.chapter4.proxy;
package com.springboot.chapter4.proxy;
/**** imports ****/
public class ProxyBean implements InvocationHandler { private Object target = null;//代理对象
private Interceptor interceptor = null;//拦截器 public ProxyBean (Object target,Interceptor interceptor){
this.target=target;
this.interceptor=interceptor;
} /**
* 处理代理对象方法逻辑
* @param proxy 代理对象
* @param method 当前方法
* @param args 运行参数
* @return 方法调用结果
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 异常标识
boolean exceptionFlag = false;
Invocation invocation = new Invocation(target, method, args);
Object retObj = null;
try {
if (this.interceptor.before()) {
retObj = this.interceptor.around(invocation);
} else {
retObj = method.invoke(target, args);
}
} catch (Exception ex) {
// 产生异常
exceptionFlag = true;
}
this.interceptor.after();
if (exceptionFlag) {
this.interceptor.afterThrowing();
} else {
this.interceptor.afterReturning();
return retObj;
}
return null;
} }
从上面例子可以看到:代理对象proxyBean定义了织入流程即invoke方法,将拦截器MyInterceptor包装到目标对象tagert的method上。
3. 测试效果
/**
* 绑定代理对象
* @param target 被代理对象
* @param interceptor 拦截器
* @return 代理对象
*/
public static Object getProxyObject(Object target, Interceptor interceptor) {
ProxyBean proxyBean = new ProxyBean(target,interceptor);//proxyBean定义了织入流程即invoke方法
// 生成代理对象
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
proxyBean);//proxyBean实现target对象的接口列表,实现方式为调用代理对象的方法时通过invoke(invoke可调用target对象的方法)调用target对象对应的方法。
// 返回代理对象
return proxy;
}
private static void testProxy() {
IHelloService helloService = new HelloServiceImpl();//被代理对象
// 按约定获取proxy
IHelloService proxy = (IHelloService) getProxyObject(
helloService, new MyInterceptor());
proxy.sayHello("zhangsan");
System.out.println("\n###############name is null!!#############\n");
proxy.sayHello(null);
}
二、Spring AOP概念和术语
Spring AOP是一种基于方法的AOP,它只能应用于方法上。在Spring中可以使用多种方式配置AOP,因为Spring Boot采用注解方式,所以这里只介绍使用@AspectJ注解的方式。AOP可以减少大量重复的工作,最为典型的应用场景就是数据库事务的管控。比如数据库的打开和关闭以及事务的提交和回滚都有流程默认给你实现。换句话说,你都不需要完成它们,你需要完成的任务是编写SQL这一步而已,然后织入流程中。

@Autowired
private UserDao = null;
...... @Transactional
public int inserUser(User user) {
return userDao.insertUser(user);
}
这里可以看到仅仅使用了一个注解@Transactional,表明该方法需要事务运行,没有任何数据库打开和关闭的代码,也没有事务回滚和提交的代码,却实现了数据库资源的打开和关闭、事务的回滚和提交。
1.AOP术语:
•连接点(join point):对应的是具体被拦截的对象,因为Spring只能支持方法,所以被拦截的对象往往就是指特定的方法,例如,我们前面提到的HelloServiceImpl的sayHello方法就是一个连接点,AOP将通过动态代理技术把它织入对应的流程中。
•切点(point cut):有时候,我们的切面不单单应用于单个方法,也可能是多个类的不同方法,这时,可以通过正则式和指示器的规则去定义,从而适配连接点。切点就是提供这样一个功能的概念。
•通知(advice):就是按照约定的流程下的方法,分为前置通知(beforeadvice)、后置通知(after advice)、环绕通知(around advice)、事后返回通知(afterReturning advice)和异常通知(afterThrowing advice),它会根据约定织入流程中,需要弄明白它们在流程中的顺序和运行的条件。
•目标对象(target):即被代理对象,例如,约定编程中的HelloServiceImpl实例就是一个目标对象,它被代理了。
•引入(introduction):是指引入新的类和其方法,增强现有Bean的功能。
•织入(weaving):它是一个通过动态代理技术,为原有服务对象生成代理对象,然后将与切点定义匹配的连接点拦截,并按约定将各类通知织入约定流程的过程。
•切面(aspect):是一个可以定义切点、各类通知和引入的内容,Spring AOP将通过它的信息来增强Bean的功能或者将对应的方法织入流程。

2.具体实现
2.1.添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2. 定义切面
首先Spring是以@Aspect作为切面声明的,当以@Aspect作为注解时,Spring就会知道这是一个切面,然后我们就可以通过各类注解(@Before、@After、@AfterReturning和@AfterThrowing)来定义各类的通知了。
package com.springboot.chapter4.aspect;
/**** imports ****/
@Component //注入到Spring IOC
@Aspect //定义切面
public class MyAspect { //引入
@DeclareParents(value= "co.springboot.chapter4.aspect.service.impl.UserServiceImpl",defaultImpl=UserValidatorImpl.class)
public UserValidator userValidator; //定义切点
@Pointcut("execution(* com.springboot.chapter4.aspect.service.impl.UserServiceImpl.printUser(..))")
public void pointCut() {
}
//无参模式
@Before("pointCut()")
public void before() {
System.out.println("before ......");
} //获取参数模式
//方式1:切点处加入对应的正则式
//方式2:对于非环绕通知还可以使用一个连接点(JoinPoint)类型的参数,通过它也可以获取参数
//正则式pointCut() && args(user)中,pointCut()表示启用原来定义切点的规则,并且约定将连接点(目标对象方法)名称为user的参数传递进来。这里要注意,JoinPoint类型的参数对于非环绕通知而言,Spring AOP会自动地把它传递到通知中;对于环绕通知而言,可以使用ProceedingJoinPoint类型的参数。
@Before("pointCut() && args(user)")
public void beforeParam(JoinPoint point, User user) {
Object[] args = point.getArgs();
System.out.println("before ......");
} //环绕通知
//注意:用环绕通知注解测试的时候总是不按顺序执行,估计是Spring版本之间的差异留下的问题,这是在使用时需要注意的。所以在没有必要的时候,应尽量不要使用环绕通知,它很强大,但是也很危险。
@Around("pointCut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around before......");
jp.proceed();//回调目标对象的原有方法 System.out.println("around after......");
} @After("pointCut()")
public void after() {
System.out.println("after ......");
} @AfterReturning("pointCut()")
public void afterReturning() {
System.out.println("afterReturning ......");
} @AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("afterThrowing ......");
}
}
上述例子用到的基础类如下

2.3 定义切点
execution(* com.springboot.chapter4.aspect.service.impl.UserServiceImpl.printUser(..))
•execution表示在执行的时候,拦截里面的正则匹配的方法;
•* 表示任意返回类型的方法;
•com.springboot.chapter4.aspect.service.impl.UserServiceImpl 指定目标对象的全限定名称;
•printUser指定目标对象的方法;
•(..)表示任意参数进行匹配。
2.4 使用@DeclareParents 定义引入
@DeclareParents,它的作用是在不侵入原有业务的前提上对原有的服务进行增强,它有两个必须配置的属性value和defaultImpl。
•value:指向你要增强功能的目标对象,这里是要增强UserServiceImpl对象,因此可以看到配置为com.springboot.chapter4.aspect.service.impl.UserServiceImpl+。
•defaultImpl:引入增强功能的类,这里配置为UserValidatorImpl,用来提供校验用户是否为空的功能。
2.5 使用切面的指示器
切面中可以使用的指示器如下,比如上述例子中arg()获取切点的参数值

3.测试效果
package com.springboot.chapter4.aspect.controller;
/**** imports ****/
// 定义控制器
@Controller
// 定义类请求路径
@RequestMapping("/user")
public class UserController { // 注入用户服务
@Autowired
private UserService userService = null; // 定义请求
@RequestMapping("/print")
// 转换为JSON
@ResponseBody
public User printUser(Long id, String userName, String note) {
User user = new User();
user.setId(id);
user.setUsername(userName);
user.setNote(note);
userService.printUser(user);// 若user=null,则执行afterthrowing方法 //测试引入
UserValidator userValidator = (UserValidator)userService;
if (userValidator.validate(user)) {
userService.printUser(user);
} return user;// 加入断点
}
}
3.1未引入效果
before ......
id =1 username =user_name_1 note =2323
after ......
afterReturning ......

3.2引入效果
引入新的接口:UserValidator
around before......
before ......
id =1 username =user_name_1 note =2323
around after......
after ......
afterReturning ......

3.4 引入原理
这里的newProxyInstance的第二个参数为一个对象数组,也就是说这里生产代理对象时,Spring会把UserService和UserValidator两个接口传递进去,让代理对象下挂到这两个接口下,这样这个代理对象就能够相互转换并且使用它们的方法了。
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
proxyBean);
3.5 织入方式
上面我们都是采用接口(如UserService)+实现类(如UserServiceImpl)的模式,这是Spring推荐的方式,本书也遵循这样的方式。但是对于是否拥有接口则不是Spring AOP的强制要求,对于动态代理的也有多种实现方式,我们之前谈到的JDK只是其中的一种,业界比较流行的还有CGLIB、Javassist、ASM等。Spring采用了JDK和CGLIB,对于JDK而言,它是要求被代理的目标对象必须拥有接口,而对于CGLIB则不做要求。因此在默认的情况下,Spring会按照这样的一条规则处理,即当你需要使用AOP的类拥有接口时,它会以JDK动态代理运行,否则以CGLIB运行。
不使用接口例子
......
// 定义控制器
@Controller
// 定义类请求路径
@RequestMapping("/user")
public class UserController {
// 使用非接口注入
@Autowired
private UserServiceImpl userService = null; // 定义请求
@RequestMapping("/print")
// 返回JSON
@ResponseBody
public User printUser(Long id, String userName, String note) {
User user = new User();
user.setId(id);
user.setUsername(userName);
user.setNote(note);
userService.printUser(user);
return user;// 加入断点测试
}
......
}

此时Spring已经使用了CGLIB为我们生成代理对象,从而将切面的内容织入对应的流程中。当使用接口时,用JDK为我们生成代理对象。
4. 多个切面
Spring提供了一个注解@Order和一个接口Ordered,我们可以使用它们的任意一个指定切面的顺序。
......
@Aspect
@Order(1)
public class MyAspect1 {
......
} //或者 ......
@Aspect
public class MyAspect1 implements Ordered {
// 指定顺序
@Override
public int getOrder() {
return 1;
}
....
}
效果如下
MyAspect1 before ......
MyAspect2 before ......
MyAspect3 before ......
测试多个切面顺序
MyAspect3 after ......
MyAspect3 afterReturning ......
MyAspect2 after ......
MyAspect2 afterReturning ......
MyAspect1 after ......
MyAspect1 afterReturning ......
约定编程与Sping AOP的更多相关文章
- convention over configuration 约定优于配置 按约定编程 约定大于配置 PEP 20 -- The Zen of Python
为什么说 Java 程序员必须掌握 Spring Boot ?_知识库_博客园 https://kb.cnblogs.com/page/606682/ 为什么说 Java 程序员必须掌握 Spring ...
- Sping AOP
Sping AOP 1.什么是AOP 面向切面编程(AOP) 是 面向对象编程的补充(OOP) 传统的业务处理代码中,通常会惊醒事务处理.日志处理等操作.虽然可以使用OOP的组合或继承来实现代码重用, ...
- 基于注解的Sping AOP详解
一.创建基础业务 package com.kang.sping.aop.service; import org.springframework.stereotype.Service; //使用注解@S ...
- SpringBoot | 1.3 约定编程Spring AOP
前言 前面聊过Spring的一个很重要的概念,IoC控制反转,接下来就是AOP了: 1. AOP切面编程 面向切面编程,是利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度 ...
- [置顶] 使用sping AOP 操作日志管理
记录后台操作人员的登陆.退出.进入了哪个界面.增加.删除.修改等操作 在数据库中建立一张SYSLOG表,使用Sping 的AOP实现日志管理,在Sping.xml中配置 <!-- Spring ...
- Sping AOP初级——入门及简单应用
在上一篇<关于日志打印的几点建议以及非最佳实践>的末尾提到了日志打印更为高级的一种方式——利用Spring AOP.在打印日志时,通常都会在业务逻辑代码中插入日志打印的语句,这实际上是和业 ...
- 第五章 面向方面编程___OOP和AOP随想
面向方面编程,又称面向切面编程(Aspect-Oriented-Programming),英文缩写 AOP,可以说是 OOP(Object-Oriented-Programming)面向对象编程的补充 ...
- 基于XML配置的Sping AOP详解
一.编写基本处理方法 package com.kang.sping.xml.aop; public class Math{ //加 public int add(int n1,int n2){ int ...
- Spring-05 -AOP [面向切面编程] -Schema-based 实现aop的步骤
一.AOP [知识点详解] AOP:中文名称面向切面编程 英文名称:(Aspect Oriented Programming) 正常程序执行流程都是纵向执行流程 3.1 又叫面向切面编程,在原有纵向执 ...
随机推荐
- Kafka的优秀设计学习
一.Kafka基础 消息系统的作用 应该大部份小伙伴都清楚,用机油装箱举个例子 所以消息系统就是如上图我们所说的仓库,能在中间过程作为缓存,并且实现解耦合的作用. 引入一个场景,我们知道中国移动,中国 ...
- (leetcode)二叉树的前序遍历-c语言实现
给定一个二叉树,返回它的 前序 遍历. 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [1,2,3] 进阶: 递归算法很简单,你可以通过迭代算法完成吗? 前序遍历 前序遍历首先 ...
- [实验吧](web)因缺思厅的绕过 源码审计绕过
0x00 直接看源码吧 早上写了个注入fuzz的脚本,无聊回到实验吧的题目进行测试,发现了这道题 地址:http://ctf5.shiyanbar.com/web/pcat/index.php 分析如 ...
- 常见的反爬措施:UA反爬和Cookie反爬
摘要:为了屏蔽这些垃圾流量,或者为了降低自己服务器压力,避免被爬虫程序影响到正常人类的使用,开发者会研究各种各样的手段,去反爬虫. 本文分享自华为云社区<Python爬虫反爬,你应该从这篇博客开 ...
- CSS入门笔记
CSS @author:伏月廿柒 Cascading Style Sheet 层叠级联样式表 CSS:表现(美化) 字体,颜色,边距,高度,宽度,背景图片,网页定位,网页浮动-- CSS发展史 CSS ...
- Future和FutureTask的区别
java中有Future和FutureTask这两个类 Future是一个接口,代表可以取消的任务,并可以获得任务的执行结果 FutureTask 是基本的实现了Future和runnable接口 ...
- Redis 集群的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所 以集群使用了主从复制模型,每个节点都会有 N-1 个复制品.
- 哪些浏览器支持HTML 5?
几乎所有的浏览器都支持HTML 5,例如Safari,Chrome,火狐,Opera,IE等.
- 介绍一下 WebApplicationContext?
WebApplicationContext 是 ApplicationContext 的扩展.它具有 Web 应用 程序所需的一些额外功能.它与普通的 ApplicationContext 在解析主题 ...
- poj_3253_priority queue
描述 农夫约翰想要修篱墙,他需要N块木板,第i块板长Li.然后他买了一块很长的板子,足够他分成N块.忽略每次锯板子带来的损失. 约翰忘记买锯子了,于是像Don借.Don要收费,每次锯一下,就要收一次板 ...