:::tip
AOP是Spring框架的两大特性之一,面向切面编程,实现原理是代理模式,有点不太理解代理模式,理一理。
:::
什么是代理模式
代理模式,简单的来说就是中转站。例如省长要开会,不会把我们老百姓叫过去,而是市长过去,市长了解之后又给县长开;一个人要买房,然后找中介,卖方的老板并不直接与买家交流,而是通过中介传达意愿交易,最终把房卖掉;媒婆(并不一定女的),通常都是两家都比较熟悉的人,要结婚办事,一些不好说的话,就是通过媒婆去传达;vpn上网,本地连接网络代理服务器,不直接与互联网进行数据交换,而是通过代理。
«interface»Internet+ method(type): typeNetWork+ method(type): typeUseUseProxyNetWork+ netWork: NetWork+ method(type): typeViewer does not support full SVG 1.1
情景:数学计算器
有两个要求
-
执行加减乘除运算
-
日志:在程序执行期间追踪正在发生的活动
需要实现加减乘除四个方法,并且在程序运行的过程需要打印日志(调用方法名,传递的参数,计算结果等)
接下来用以下各种各种方式实现。
普通实现
1
2
3
4
5
6
7
8
9
|
public class App
{
public static void main( String[] args )
{
MathI mathI = new MathImpl();
int result = mathI.sub(1,2);
System.out.println(result);
}
}
|
1
2
3
4
5
6
|
public interface MathI {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
|
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
|
public class MathImpl implements MathI{
@Override
public int add(int i, int j) {
System.out.println("method:add,arguments:"+i+","+j);
int result = i + j;
System.out.println("method:add,result:"+result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("method:sub,arguments:"+i+","+j);
int result = i - j;
System.out.println("method:sub,result:"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("method:mul,arguments:"+i+","+j);
int result = i * j;
System.out.println("method:mul,result:"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("method:div,arguments:"+i+","+j);
int result = i / j;
System.out.println("method:div,result:"+result);
return result;
}
}
|
一个计算接口中,定义了加减乘除四个方法,计算器实现计算类,并且在运行的时候打印了所调用的方法,传入的参数,计算的结果,每个方法体里面的结构都是差不多的,只是调用不同的方法,结果,标识有些不同。业务代码与非业务代码也混到了一起,显得混乱,我看着还挺整齐的。
静态代理实现
1
2
3
4
5
6
7
8
9
|
public class App
{
public static void main( String[] args )
{
ProxyUtil proxy = new ProxyUtil(new MathImpl());
int result = proxy.sub(1,2);
System.out.println(result);
}
}
|
1
2
3
4
5
6
|
public interface MathI {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
|
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
|
public class MathImpl implements MathI{
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
|
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
|
public class Proxy implements MathI{
public MathImpl mathImpl;
public Proxy(MathImpl mathImpl) {
this.mathImpl = mathImpl;
}
@Override
public int add(int i, int j) {
System.out.println("method:add,arguments:"+i+","+j);
int result = mathImpl.add(i,j);
System.out.println("method:add,result:"+result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("method:sub,arguments:"+i+","+j);
int result = mathImpl.sub(i,j);
System.out.println("method:sub,result:"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("method:mul,arguments:"+i+","+j);
int result = mathImpl.mul(i,j);
System.out.println("method:mul,result:"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("method:div,arguments:"+i+","+j);
int result = mathImpl.div(i,j);
System.out.println("method:div,result:"+result);
return result;
}
}
|
这种实现方式看起来并没有改进多少,相反代码更加多了,但是使MathImpl的业务代码和非业务代码隔离了,也算是进步了吧。
JDK动态代理
1
2
3
4
5
6
7
8
9
10
|
public class App
{
public static void main( String[] args )
{
ProxyUtil proxy = new ProxyUtil(new MathImpl());
MathI math = (MathI) proxy.getProxy();
int result = math.add(1,2);
System.out.println(result);
}
}
|
1
2
3
4
5
6
|
public interface MathI {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
|
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
|
public class MathImpl implements MathI{
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
|
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
|
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyUtil {
public MathImpl mathImpl;
public ProxyUtil(MathImpl mathImpl) {
this.mathImpl = mathImpl;
}
public Object getProxy() {
//第一个参数类加载器。用来加载代理对象所属类。
ClassLoader loader = this.getClass().getClassLoader();
//第二个参数interfaces,目标对象实现所有的接口的class对象。
Class[] interfaces = mathImpl.getClass().getInterfaces();
//第三个参数代理对象如何实现目标对象的方法。
return Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method:"+method.getName()+",args:"+ Arrays.toString(args));
Object obj = method.invoke(mathImpl,args);
System.out.println("method:"+method.getName()+",result:" + obj.toString());
return obj;
}
});
}
}
|
cglib动态代理
CGlib动态代理和JDK动态代理相区别的地方就是,CGlib必须要包含继承,JDK必须包含接口实现。
AspectJ实现
AspectJ并不是Spring自带的,是实现AOP的最好实现方式。
首先在pom.xml中添加以下依赖
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
|
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
|
然后再创建applicationContext.xml,内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启aspectJ自动代理-->
<aop:aspectj-autoproxy />
<context:component-scan base-package="org.maven.spring">
</context:component-scan>
</beans>
|
1
2
3
4
5
6
7
8
9
10
11
|
public class App
{
public static void main( String[] args )
{
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
MathI mathI = ac.getBean("mathImpl",MathI.class);
int result = mathI.add(1,2);
System.out.println(result);
}
}
|
1
2
3
4
5
6
|
public interface MathI {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
|
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
|
@Component
public class MathImpl implements MathI{
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
|
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
|
//标识为一个切面
@Component
@Aspect
public class MyloggerAspect {
//切面里面的方法就叫做通知
//@Before将方法指定为前置通知,里面些切入表达式
//通知需要关注切入点
//当通知执行时就会把方法的一些信息添加到JoinPoint中
@Before(value = "execution(* org.maven.spring.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
//获取方法的参数
Object[] args = joinPoint.getArgs();
//getSignation将切面中的名字进行封装
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("method:"+methodName+",args:"+ Arrays.toString(args));
}
//返回通知
//可用returning设置接受方法的变量名
//在方法的参数中也应该改添加上
@AfterReturning(value = "execution(* org.maven.spring.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint , Object result) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println("method:" + methodName+",result:" + result);
}
}
|
切入点表达式
:::tip
通常的形式
1
|
execution(public int org.maven.spring.MathImpl.add(int,int))
|
可以改写成
1
|
execution(* org.maven.spring.*.*(..))
|
第一个代表任意返回值任意类型
第二个代表此包下任意类
第三个*代表任意方法
两个点代表任意参数
:::
切入点简化方式
:::tip
@Pointcut(value = “execution(* org.maven.spring..(..))”)
public void proxy(){}
@Before(value=“proxy()”)
public void auto(){}
:::
五种通知类型
:::tip
1
2
3
4
5
6
7
8
9
|
@Before(value = "execution(* org.maven.spring.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
//获取方法的参数
Object[] args = joinPoint.getArgs();
//getSignation将切面中的名字进行封装
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("method:"+methodName+",args:"+ Arrays.toString(args));
}
|
:::
:::tip
1
2
3
4
|
@After(value = "execution(* org.maven.spring.*.*(..))")
public void after() {
System.out.println("后置通知");
}
|
:::
:::tip
1
2
3
4
5
6
|
@AfterReturning(value = "execution(* org.maven.spring.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint , Object result) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println("method:" + methodName+",result:" + result);
}
|
:::
:::tip
1
2
3
4
5
|
//可捕捉是什么错误,用throwing
@AfterThrowing(value = "execution(* org.maven.spring.*.*(..))",throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("有异常"+e);
}
|
:::
:::tip
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Around(value = "execution(* org.maven.spring.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
//前置通知
result = joinPoint.proceed();//执行方法
//返回通知
return result;
} catch (Throwable e) {
//异常通知
} finally {
//后置通知
}
return -1;
}
|
:::
spring框架之xml实现
applicationContext.xml配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.maven.spring"></context:component-scan>
<aop:config>
<aop:aspect ref="myloggerAspect">
<aop:before method="before" pointcut="execution(* org.maven.spring.*.*(..))"/>
<aop:after-returning method="afterReturning"
pointcut="execution(* org.maven.spring.*.*(..))"
returning="result"
/>
</aop:aspect>
</aop:config>
</beans>
|
除了切面代码有所改动,其余与AspectJ实现相同。
1
2
3
4
5
6
7
8
9
10
11
12
|
@Component
public class MyloggerAspect {
public void before(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("method:"+name+",args:"+Arrays.toString(args));
}
public void afterReturning(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println("method:"+name+",result:"+result);
}
}
|
参考连接
尚硅谷spring