内容简介:面向切面编程(AOP)是通过另一种思考方式来对面向对象编程(OOP)的补充。在抽象的结构中,OOP模块的基本单元是类,而AOP的基本单元是面。AOP的面能够跨越多个类型和对象来达成模块化。下面是根据我的理解画的图:这是一个简单的MVC结构,不同的模块之间根据类来分离。但是AOP的切面却可以跨越多个模块。图中的示例表示UserAOP跨越了整个Controller模块。当然他也可以同时跨越Model模块。这取决于AOP的实际业务需求。
面向切面编程(AOP)是通过另一种思考方式来对面向对象编程(OOP)的补充。在抽象的结构中,OOP模块的基本单元是类,而AOP的基本单元是面。AOP的面能够跨越多个类型和对象来达成模块化。
下面是根据我的理解画的图:
这是一个简单的MVC结构,不同的模块之间根据类来分离。但是AOP的切面却可以跨越多个模块。图中的示例表示UserAOP跨越了整个Controller模块。当然他也可以同时跨越Model模块。这取决于AOP的实际业务需求。
AOP提供了一个不同的编程思路,不过springIoc并没有依赖AOP,对于Ioc来说,AOP可以提供支持但不是必须。
AOP结构
- 切面(Aspect):跨越多个类别的关注点的模块化。事务管理是企业 Java 应用程序中对切面应用最多的例子。在SpringAOP中,切面是使用常规类(基于模式的方法)或使用@Aspect注释(@AspectJ样式)注释的常规类来实现的 。
- 连接点(JoinPoint):程序执行期间的一个点。在springAOP中表示AOP程序执行的点。
- 通知(Advice):连接点在特定状态下执行的操作。其中包括了“around”、“before”和“after”等不同类型的通知。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
- 切入点(Pointcut):和连接点匹配的断言。切入点确定了AOP程序的入口,并在这之后和连接点相连并执行对应的连接点方法。切入点表达式如何和连接点匹配是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框架一样,在运行时完成织入。
其中切入点可以和连接点合并,直接在通知中表示:
@Before("execution(com.dust.controller. . (..))")
/}
该写法等价于:
@Pointcut("execution(com.dust.controller.
.(..))")
public void point(){}
@Befor("point()")
public void before(){/
方法体 */}通知
在AOP中,连接点与切入点的关联关系以及相应的判断规则是AOP的核心。切入点确定了AOP入口,但是具体要执行哪部分则由连接点决定。连接点是方法执行的入口。
- @Before:前置通知,在连接点前执行。这个通知不能阻碍通知之前的程序执行。
@Before("execution(* com.dust.controller.*.*(..))")
public void beforController() {
//在Controller被调用前
}
复制代码
- @AfterReturning:后置通知,在连接点之后执行,但是可以获取到连接点的返回值。
这里可以知道,对AOP来说,最小的单元是方法。AOP只能在方法和方法之间切入,而不能切入方法本身
@AfterReturning("execution(* com.dust.controller.*.*(..))")
public void afterController() {
//在Controller执行完之后
}
复制代码
对方法返回值的获取:
@AfterReturning(
pointcut = "execution(* com.dust.controller.*.*(..))",
returning = "retVal")
public void afterController(Object retVal) {
//对方法返回值的获取
System.out.println(retVal.toString());
}
复制代码
其中returning中的参数名称必须要和advice方法的参数名称相同,当方法执行返回时,返回值将作为相应的参数值传递给advice方法。如果方法没有返回值,则该参数为null。
- @AfterThrowing:异常通知,当方法抛出异常的时候执行。
@AfterThrowing("execution(* com.dust.controller.*.*(..))")
public void afterController() {
//在Controller抛出异常后执行
}
复制代码
也可以设置到抛出给定异常时才执行advice。
@AfterThrowing(
pointcut = "execution(* com.dust.controller.*.*(..))",
throwing = "ex")
public void afterController(NullPointerException ex) {
//抛出空指针异常时执行advice
}
复制代码
- @After:最终通知,在连接点之后执行。不论返回点是正常还是异常。通常用来释放资源。通常在这里需要进行正常和异常的返回条件。
@After("execution(* com.dust.controller.*.*(..))")
public void afterController() {
//最终执行通知
}
复制代码
- @Around:环绕通知。包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。他可以确定方法何时、如何甚至是否执行。环绕通知使用一个代理ProceedingJoinPoint类型的对象来管理目标对象,所以此通知的第一个参数必须是ProceedingJoinPoint类型,在通知体内,调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个Object[]对象-该数组中的值将被作为方法执行时的参数。
通知参数
所有通知方法都可以声明一个类行为org.aspectj.lang.JoinPoint的参数。
环绕通知声明的参数为ProceedingJoinPoint,因为需要执行ProceedingJoinPoint的proceed()方法。而其他的通知则不需要。
JoinPoint提供了很多有用的方法:
- getArgs()(返回方法参数):返回该方法的参数集合,是一组Object[]
- getThis()(返回代理对象):获取代理对象本身
- getTarget()(返回目标):获取连接点所在的目标对象
- getSignature()(返回正在被通知的方法相关信息):获取连接点的方法签名对象
- toString()(打印出正在被通知的方法的有用信息)
传入参数
通常通知方法获取方法参数除了上述通过JoinPoint获取外还可以通过pointcut获取
@Pointcut("execution(* com.example.springdemo.controller.*.*(..)) && args(address, text, ..)")
public void inController(String address, String text) {}
@Before("inController(address, text)")
public void beforController(String address, String text) {
System.out.println("在执行控制器之前,获取参数{address:" + address + ",text:" + text + "}");
}
复制代码
args(address, text, ..)切入点表达式匹配切入点方法的参数,而后传入给advice方法。 这是要求所有和该切入点匹配的连接点都需要接收参数,还可以单单在连接点接收参数:
@Pointcut("execution(* com.example.springdemo.controller.*.*(..))")
public void inController() {}
@Before("inController() && args(address, text, ..)")
public void beforController(String address, String text) {
System.out.println("在执行控制器之前,获取参数{address:" + address + ",text:" + text + "}");
}
复制代码
其他参数:代理对象(this),目标对象(target)和注释(@within, @target, @annotation, @args)都可以以类似的方式绑定。
例子:使用AOP来重试事务
由于并发问题:死锁。可能导致业务执行失败。下次执行又有可能执行成功,因此对于这种操作不希望将重试交给用户来执行,这个可以交由系统来执行,这样对于用户来说他还是一次就执行成功了。
由于要尝试执行多次process(),因此使用@Around环绕通知
@Aspect
@Configuration
public class AOPRedo {
//默认最大重试次数
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
@Around("execution(* com.example.springdemo.controller.RestAPIController.*(..))")
public Object apiAroundController(ProceedingJoinPoint pjp) {
int num = 0;
Throwable throwable;
do {
num++;
try {
return pjp.proceed();
} catch (Throwable th) {
System.out.println("尝试捕获");
throwable = th;
}
} while (num <= maxRetries);
return null;
}
}
复制代码
其中重试次数可以交给配置文件,通过@PropertySource来导入配置信息。
@RestController
@RequestMapping("api")
public class RestAPIController {
@Autowired
EmailService emailService;
private int count = 0;
@RequestMapping("email")
public String email(String address, String info) throws NullPointerException {
if (count++ < 1) {
throw new NullPointerException();
}
return emailService.setEmail(address,info) + "执行次数:" + count;
}
}
复制代码
执行结果
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【每日笔记】【Go学习笔记】2019-01-04 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-02 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-07 Codis笔记
- vue笔记3,计算笔记
- Mysql Java 驱动代码阅读笔记及 JDBC 规范笔记
- 【每日笔记】【Go学习笔记】2019-01-16 go网络编程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
PHP and MySQL Web Development
Luke Welling、Laura Thomson / Sams / July 25, 2007 / $49.99
Book Description PHP and MySQL Web Development teaches you to develop dynamic, secure, commerical Web sites. Using the same accessible, popular teaching style of the three previous editions, this b......一起来看看 《PHP and MySQL Web Development》 这本书的介绍吧!