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
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
package 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*(..))")
public void My() {
}
//定义前置通知,输出当前时间2019.1.1 00:00:00
/********** Begin **********/
@Before("execution(* Educoder.BlogService.service*(..))")
public void before() {
System.out.println("当前时间2019.1.1 00:00:00");
}
/********** End **********/
//定义后置通知,输出当前时间2019.1.1 00:01:00,执行耗时60000
/********** Begin **********/
@After("execution(* Educoder.BlogService.service*(..))")
public void After() {
System.out.println("当前时间2019.1.1 00:01:00,执行耗时60000");
}
/********** End **********/
}

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
34
package 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.ProxynewProxyInstance方法中;
  • 通过调用java.lang.reflect.ProxynewProxyInstance方法获得动态代理对象;
  • 通过代理对象调用目标方法。

任务代码

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
package educoder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyProxy {
public static UserService createUserService() {
//请在以下提示框内填写你的代码
/**********Begin**********/
//目标类
UserServiceImpl userserviceImpl = new UserServiceImpl();
//切面类
Authority authority = new Authority();
//代理类:将目标类与切面类结合
UserService proxyUserService = (UserService) Proxy.newProxyInstance(MyProxy.class.getClassLoader(), userserviceImpl.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//目标方法执行前
authority.before();
//放行目标方法
Object invoke = method.invoke(userserviceImpl, args);
return invoke;
}
});
//返回代理对象
return proxyUserService;

/**********End**********/
}
}

4.AOP实现原理-CgLib动态代理

CGLIB代理模拟AOP实现主要是采用字节码增强框架cglib,在运行时创建目标类的子类,从而对目标类进行增强。

CGLIB动态代理步骤

  • 定义一个org.springframework.cglib.proxy.MethodInterceptor接口的实现类,重写intercept方法;
  • 获取org.springframework.cglib.proxy.Enhancer类的对象;
  • 分别调用Enhancer对象的setSuperclasssetCallback方法,使用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
37
package 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**********/
}
}

实训地址:头歌编程


SSM框架-Spring 面向切面编程
http://example.com/2021/09/09/Spring面向切面编程/
Author
lzdong
Posted on
September 9, 2021
Licensed under