SSM框架-Spring 面向切面编程
Spring 面向切面编程
OOP
允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting
)代码,在OOP
设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP
(面向切面编程)技术则恰好解决了这些不足,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect
”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
1.使用前后置通知统计所有方法的执行时间
AOP基本概念
Aspect
(切面): 切面由切点 (Pointcut
) 和增强/通知 (Advice) 组成,它既包括了横切逻辑的定义、也包括了连接点的定义;Joint point
(连接点):能够被拦截的地方:Spring AOP
是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点;Pointcut
(切点):具体定位的连接点,上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点;Advice
(通知/增强):表示添加到切点的一段逻辑代码,并定位连接点的方位信息。简单来说就定义了是干什么的,具体是在哪干;Spring AOP
提供了5种Advice
类型给我们,分别是:前置(Before
)、后置(After
)、返回(AfterReturning
)、异常(AfterThrowing
)、环绕(Around
);Target
(目标对象):织入Advice
的目标对象;Weaving
(织入):将增强/通知添加到目标类的具体连接点上的过程。
切点完整表达式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
参数 | 参数说明 |
---|---|
modifiers-pattern | 修饰符 可选 public private protected |
declaring-type-pattern | 方法的声明类型 |
name-patterm | 方法名称类型,例 set* 则表示以set开头的所有的方法名称 |
param-pattern | 参数匹配:(..) 表示任意多个参数,每个参数任意多个类型,(*,String) 表示两个参数,第一个是任意类型,第二个是String |
throws-pattern | 异常的匹配模式 |
ret-type-pattern | 返回类型 必选 * 代表任意类型 |
五种通知
前置通知:在连接点前面执行,前置通知不会影响连接点的执行;
后置通知:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容;
异常返回通知:在连接点抛出异常后执行;
正常返回通知:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行;
环绕通知:最为强大的通知,它能够让你编写的逻辑将被通知的目标方法完全包裹起来。实际上就像在一个通知方法中同时编写前置通知和后置通知。定义通知的时候在通知方法中添加了入参ProceedingJoinPoint
,这个参数是必须写的。因为需要在通知中使用ProceedingJoinPoint.proceed()
调用目标方法。
任务代码
1 |
|
2.使用环绕通知统计所有带参方法的执行时间
环绕通知和前后通知的区别
目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知是不能决定的,他们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的;
环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用。
任务代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34package Educoder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@Component("BlogAdvice")
@Aspect
public class BlogAdvice {
//定义切点
@Pointcut("execution(* Educoder.BlogService.service*(String))")
public void My() {
}
//定义环绕通知,实现统计目标类所有带参方法运行时长
@Around("My()")
public void around(ProceedingJoinPoint point) throws ParseException, InterruptedException {
/********** Begin **********/
try {
point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.print("程序a执行耗时60000");
/********** End **********/
}
}
第二关有点问题,我看评论区,大家都不知道怎么让Pointcut
只锁定输出业务管理一的,网上也没找到答案,所以要在命令行里面打开BlogService
类把业务功能2的输出语句注释掉。
3.AOP实现原理-JDK动态代理
代理模式(Proxy)
代理模式就是给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用,在不改变目标对象方法的情况下对方法进行增强。
AOP实现的两种方式
切面类中的方法会根据相应的策略对目标对象进行增强,Spring AOP
使用JDK
动态代理或CGLIB
为给定目标对象创建代理。如果要代理的目标对象实现了至少一个接口,则AOP
默认使用JDK
动态代理,否则使用CGLIB
代理。
JDK动态代理步骤
Proxy.newInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- 第一个参数
ClassLoader loader
:类加载器,一般写当前类; - 第二个参数
Class<?>[] interfaces
:代理类所需要实现的接口; - 第三个参数
InvocationHandler h
:处理类,一般写匿名类。
步骤
- 定义一个
java.lang.reflect.InvocationHandler
接口的实现类,重写invoke
方法; - 将
InvocationHandler
对象作为参数传入java.lang.reflect.Proxy
的newProxyInstance
方法中; - 通过调用
java.lang.reflect.Proxy
的newProxyInstance
方法获得动态代理对象; - 通过代理对象调用目标方法。
任务代码
1 |
|
4.AOP实现原理-CgLib动态代理
CGLIB
代理模拟AOP
实现主要是采用字节码增强框架cglib
,在运行时创建目标类的子类,从而对目标类进行增强。
CGLIB动态代理步骤
- 定义一个
org.springframework.cglib.proxy.MethodInterceptor
接口的实现类,重写intercept
方法; - 获取
org.springframework.cglib.proxy.Enhancer
类的对象; - 分别调用
Enhancer
对象的setSuperclass
和setCallback
方法,使用create
方法获取代理对象; - 通过代理对象调用目标方法。
任务代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37package educoder;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy {
public static UserServiceImpl createUserService(){
//请在以下提示框内填写你的代码
/**********Begin**********/
//目标类
UserServiceImpl user=new UserServiceImpl();
//切面类
Authority authority=new Authority();
//cglib核心类
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(user.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//目标方法执行前
authority.before();
//放行目标方法
Object invoke = methodProxy.invokeSuper(o,objects);
return invoke;
}
});
//获取代理对象
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
//返回代理对象
return proxy;
/**********End**********/
}
}实训地址:头歌编程
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!