spring框架-AOP切面编程

AOP(Aspect Orient Programming),面向切面编程

切面:公共的,通用的,重复的功能成为切面,面向切面编程就是将切面提取出来,单独开发,在需要的时候织入。

手写AOP框架

​ 业务:图书购买业务

​ 切面:事物

1)第一个版本:业务和切面紧耦合在一起,没有拆分

2)第二个版本:使用子类代理的方式拆分业务和切面

3)第三个版本:使用静态代理拆分业务和切面,业务和业务接口已分开,此时切面紧耦合在业务中

4)第四个版本:使用静态代理拆分业务和业务接口,切面和切面接口

5)第五个版本:使用动态代理完成第四个版本的优化

第一个版本:业务和切面紧耦合在一起,没有拆分

1
2
3
4
5
6
7
8
9
10
11
public class BookServiceImpl{
public void buy(){
try{
System.out.println("事务开启。。。。");
System.out.println("图书购买业务功能实现。。。。");
System.out.println("事务提交。。。。");
}catch(Exception e){
System.out.println("事务回滚。。。。");
}
}
}

第二个版本:使用子类代理的方式拆分业务和切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class BookServiceImpl{
public void buy(){
System.out.println("图书购买业务功能实现。。。。");
}
}

//子类就是代理类,将父类的图书购买功能添加事务切面
public class SubBookServiceImpl extends BookServiceImpl{
@Override
public void buy(){
try{
// 事务切面
System.out.println("事务开启。。。。");
// 主业务实现
super.buy();
// 事务切面
System.out.println("事务提交。。。。");
}catch(Exception e){
System.out.println("事务回滚。。。。");
}
}

}

第三个版本:使用静态代理拆分业务和切面,业务和业务接口已分开,此时切面紧耦合在业务中

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public interface Service{
//规定业务功能
void buy();
}

public class BookSeviceImpl implements Service{
@Override
public void buy(){
System.out.println("图书购买业务功能实现。。。。");
}
}

public class ProductServiceImpl implements Service{
@Override
public void buy(){
System.out.println("商品购买业务功能实现。。。。");
}
}

public class Agent implements Service{
//设计成员变量的类型为接口,为了灵活切换目标对象
public Service target;

//使用构造方法传入目标对象
public Agent(Service target){
this.target = target;
}

@Override
public void buy(){
try{
// 切面功能
System.out.println("事务开启。。。。");
// 业务功能
super.buy();
// 切面功能
System.out.println("事务提交。。。。");
}catch(Exception e){
System.out.println("事务回滚。。。。");
}
}
}


# Test
public class Mytest{
@Test
public void test(){
Service agent = new Agent(new BookSeviceImpl());
agent.buy();

Service agent = new Agent(new ProductServiceImpl());
agent.buy();
}
}

第四个版本:使用静态代理拆分业务和业务接口,切面和切面接口

上个版本只实现了事务切面,如果要换成日志切面实现呢?

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
###########################业务接口#####################
public interface Service{
//规定业务功能
void buy();
}

public class BookSeviceImpl implements Service{
@Override
public void buy(){
System.out.println("图书购买业务功能实现。。。。");
}
}

public class ProductServiceImpl implements Service{
@Override
public void buy(){
System.out.println("商品购买业务功能实现。。。。");
}
}

#####################切面接口AOP########################
public interface AOP{
default void before(){};
default void after(){};
default void exception(){};
}

public class LogAop implements AOP{
@Override
public void before(){
System.out.println("前置日志输出。。。。");
}
}

public class TransAop implements AOP{
@Override
public void before(){
System.out.println("事务开启。。。。");
}

@Override
public void after(){
System.out.println("事务提交。。。。");
}

@Override
public void exception(){
System.out.println("事务回滚。。。。");
}
}

#####################代理对象Agent########################
public class Agent implements Service{
//传入业务对象,切面对象
Service target;
Aop aop;

//使用构造方法初始化业务对象和切面对象
public Agent(Service target, AOP aop){
this.target = target;
this.aop = aop;
}

@Override
public void buy(){
try{
//切面
aop.before(); //事务 日志
//业务
target.buy(); // 图书 商品
//切面
aop.after(); // 事务
}catch(Exception e){
aop.exception();
}
}
}


## test
public class Mytest{
@Test
public void test{
Serive agent = new Agent(new BookServiceImpl(),new LogAop());
agent.buy();

Serive agent = new Agent(new ProductServiceImpl(),new TransAop());
agent.buy();
}
}

## 切入多个切面
## test
public class Mytest{
@Test
public void test{
Serive agent = new Agent(new BookServiceImpl(),new LogAop());
agent.buy();

Serive agent1 = new Agent(agent,new TransAop());
agent1.buy();
}
}

第五个版本:使用动态代理完成第四个版本的优化

业务功能的增加

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
###########################业务接口#####################
public interface Service{
//规定业务功能
void buy();

//增加有参返回值的方法测试代理功能
default String show(int age){return null};
}

public class BookSeviceImpl implements Service{
@Override
public void buy(){
System.out.println("图书购买业务功能实现。。。。");
}
}

public class ProductServiceImpl implements Service{
@Override
public void buy(){
System.out.println("商品购买业务功能实现。。。。");
}

@Override
public String show(int age){
System.out.println("这是show()方法调用。。。。");
return "abcd";
}
}

#####################切面接口AOP########################
public interface AOP{
default void before(){};
default void after(){};
default void exception(){};
}

public class LogAop implements AOP{
@Override
public void before(){
System.out.println("前置日志输出。。。。");
}
}

public class TransAop implements AOP{
@Override
public void before(){
System.out.println("事务开启。。。。");
}

@Override
public void after(){
System.out.println("事务提交。。。。");
}

@Override
public void exception(){
System.out.println("事务回滚。。。。");
}
}

####################动态代理####################
public class ProxyFactory{
public static Object getAgent(Service target,Aop aop){
//返回的是生成的动态代理对象
return Proxy.newProxyInstance(
//类加载器
target.getClass().getClassLoader(),
// 目标对象实现的所有的接口
target.getClass().getInterfaces(),
//代理功能实现
new InvocationHandler(){
@Override
public Object invoke(
//生成的代理对象
Object proxy
//正在被调用的目标方法buy(),show()
,Method method
//目标方法的参数
,Object[] args) throws Throwable{
//切面
aop.before();//事务 日志
//业务
Object obj = method.invoke(target,args);
//切面
aop.after();
//切面
return obj;//目标方法的返回值
}
}
)
}
}

#test
public class Mytest{
@Test
public void test(){
Service agent = ProxyFactory.getAgent(new BookServiceImpl(),new TransAop());
agent.buy();
String s = agent.show(22);
}
}


AspectJ框架

AspectJ中常用的通知有几种类型:

0)@Aspect:作用是把当前类标识为一个切面供容器读取

1)前置通知@Before

2)后置通知@AfterReturning

3)环绕通知@Around

4)最终通知@After

5)定义切入点@Pointcut

使用@Pointcut注解,创建一个空方法,此方法的名称就是别名

@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。

使用pointcut代码:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public void SomeServiceImpl implements SomeService{
@Override
public String doSome(String name,int age){
System.out.println("doSome业务方法执行......."+name);
return "abcd";
}
}



@Aspect
public class MyAspect{
/*
前置通知切面方法规范
1)访问权限是public
2)方法的返回值是void
3)方法名称自定义
4)方法没有参数,如果有也只能是JoinPoint类型
5)必须使用@Before注解来声明切入的时机
参数:value 指定切入点表达式
*/
@Before(value = "execution(public String com.yuting.SomeSeriveImpl.dosome(String,int))")
# @Around(value="myCut()")
public void myBefore(){
System.out.println("切面方法中的前置通知");
}
/*
后置通知切面方法规范
1)访问权限是public
2)方法的返回值是void
3)方法名称自定义
4)方法没有参数,如果有也只能是JoinPoint类型
5)必须使用@After注解表明是最终通知
参数:value 指定切入点表达式
*/
@After(value="execution(* com.yuting.s01.*.*(..))")
# @Around(value="myCut()")
public void myAfter(){
System.out.println("切面方法中的最终通知");
}


@AfterReturning(value="execution(* com.yuting.s01.*.*(..))",returning="obj")
# @Around(value="myCut()")
public void myAfterReturning(Object obj){
System.out.println("后置通知的功能");
}

/*
环绕通知切面方法规范
1)访问权限是public
2)方法的返回值是目标方法的返回值
3)方法名称自定义
4)方法有参数,此参数就是目标方法
5)必须使用@Around注解表明是环绕通知
参数:value 指定切入点表达式
*/
@Around(value="execution(* com.yuting.s01.*.*(...))")
# @Around(value="myCut()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知中的前置通知功能");
Object obj = pjp.proceed(pjp.getArgs());
System.out.println("环绕通知中的后置通知功能");
return obj.toString().toUpperCase(); # 改变了目标方法的返回值
}
}

@PointCut(value = "execution(* com.yuting.s01.*.*(...))")
public void myCut(){

}


注解配置
applicationContext.xml
<!--基于注解的访问要添加包扫描-->
<context:component-scan base-package="com.yuting"></context:component-scan>
<!--绑定-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>


使用annotation代码:

@annotation: 元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.@Target
说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标

2.@Retention
@Retention定义了该Annotation被保留的时间长短==:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotationclass在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

3.@Documented
@Documented:用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员

4.@Inherited
@Inherited :元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

5.自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、ClassStringenum)。可以通过default来声明参数的默认值。

定义注解格式:
public @interface 注解名 {定义体}

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//注解实体类
package com.trip.demo;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface SMSAndMailSender {
/*短信模板String格式化串*/
String value() default "";

String smsContent() default "";

String mailContent() default "";
/*是否激活发送功能*/
boolean isActive() default true;
/*主题*/
String subject() default "";
}


//切面类
@Aspect
@Component("smsAndMailSenderMonitor")
public class SMSAndMailSenderMonitor {

private Logger logger = LoggerFactory.getLogger(SMSAndMailSenderMonitor.class);
/**
* 在所有标记了@SMSAndMailSender的方法中切入
* @param joinPoint
* @param result
*/
@AfterReturning(value="@annotation(com.trip.demo.SMSAndMailSender)", returning="result")//有注解标记的方法,执行该后置返回
public void afterReturning(JoinPoint joinPoint , Object result//注解标注的方法返回值) {
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
Method method = ms.getMethod();
boolean active = method.getAnnotation(SMSAndMailSender.class).isActive();
if (!active) {
return;
}
String smsContent = method.getAnnotation(SMSAndMailSender.class).smsContent();
String mailContent = method.getAnnotation(SMSAndMailSender.class).mailContent();
String subject = method.getAnnotation(SMSAndMailSender.class).subject();

}

/**
* 在抛出异常时使用
* @param joinPoint
* @param ex
*/
@AfterThrowing(value="@annotation(com.trip.order.monitor.SMSAndMailSender)",throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex//注解标注的方法抛出的异常) {
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
Method method = ms.getMethod();
String subject = method.getAnnotation(SMSAndMailSender.class).subject();

}
}
//实体类中使用该注解标注方法
@Service("testService ")
public class TestService {
@Override
@SMSAndMailSender(smsContent = "MODEL_SUBMIT_SMS", mailContent =
"MODEL_SUPPLIER_EMAIL", subject = "MODEL_SUBJECT_EMAIL")
public String test(String param) {
return "success";
}


记得在配置文件中加上:

<aop:aspectj-autoproxy proxy-target-class="true"/>


觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭



wechat pay



alipay

spring框架-AOP切面编程
http://yuting0907.github.io/2022/09/16/spring框架-AOP切面编程/
作者
Echo Yu
发布于
2022年9月16日
许可协议