动态代理与Spring AOP

前言

在Spring Core中有两个核心部分,Spring IOC和Spring AOP,Spring IOC叫做控制反转,依赖注入(DI).Spring AOP叫做面向切面编程.而Spring AOP的底层实现实际上就是JDK中的代理模式.

从静态代理模式说起…

什么是代理模式?下面举一个简单的例子
某歌手如果要开一场演唱会,假设要经历如下步骤:
1.选择开演唱会的地点
2.和举办方协商出场费
3.演唱
4.收取出场费
在这四步骤,一般歌手只需要去进行第三步–>演唱,因为他需要一个良好的精神状态,如果所有事都交给歌手去做,肯定会影响他的发挥,那么其他的事由谁来做呢,这时就出现了一个代理对象–>经纪人
在歌手演唱之前他会提前帮助歌手选好演唱会的地点,协商好出场费,然后让歌手去唱歌,演唱结束后收取出场费,最终实现分红.
这一过程实际上就是一个代理的过程,歌手只需要做好自己的事情,其他多余但也有必要的事交给代理去做,这就是代理模式

具体业务中的静态代理模式举例

如果上面的例子还是不太理解,那我再说一个
假设我们的主营业务是CRUD,即增删查改.但是现在在每一个主营业务前,我们都需要加入一个安全性检测.这时怎么办呢?
按照以往思路,那还不简单吗?直接在每一个CRUD的方法之前在加入一个安全性检测的方法呗?那如果有多个方法呢?加入多个吗?
答案并不是这样的,因为这样会造成主营业务代码的频繁更改,使得主营业务逻辑混乱,加入更多的方法后甚至会分不清哪些是我们的主营业务哪些是辅助业务,那该如何解决?
此时就要用到代理类,在这个类中同样实现所有的主营业务,并且适时加入辅助业务,例如安全性检测,如果有其他需求只需变动此代理类,在核心业务不变的情况下我们永远不需要改动主营业务的代码
这就是代理模式,下面用代码来具体说明一下:
首先是我们的业务接口 : 定义具体要做的主营业务

1
2
3
4
5
6
7
//业务接口
public interface RealPro {
void create();
void delete();
void select();
void modify();
}

主营类实现业务接口 :该类中是我们主营工作的业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RealProExecute implements RealPro {

public void create() {
System.out.println("=====增加=======");
}

public void delete() {
System.out.println("=====删除=======");
}

public void select() {
System.out.println("=====选择=======");
}

public void modify() {
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
//代理类
public class ProxyProExecute implements RealPro {

//将真实业务对象传入
private RealProExecute realProExecute;

public ProxyProExecute(RealProExecute realProExecute){
this.realProExecute = realProExecute;
}

public void create() {
safeCheck();
this.realProExecute.create();
}

public void delete() {
safeCheck();
this.realProExecute.delete();
}

public void select() {
safeCheck();
this.realProExecute.select();
}

public void modify() {
safeCheck();
this.realProExecute.modify();
}

public void safeCheck(){
System.out.println("安全性检测");
}

}

代理类同样继承了业务接口,然后传入了主营业务的对象,这样代理类在实现业务接口中的方法时就可以将主营业务中对应的具体的方法实现调入进来,不光如此,此时还可以加入辅助业务,即代理业务(本例中的safecheck),完成代理服务
测试类

1
2
3
4
5
6
7
public class Testsproxy {

public static void main(String[] args) {
RealPro proxyProExecute = new ProxyProExecute(new RealProExecute());
proxyProExecute.create();
}
}

此时进行代码测试,我们只需要创建一个代理类(向上转型)然后调用其中的业务方法,该业务方法中有真实业务对象的方法,还有添加的辅助方法,以此便达到了代理的目的,这也就是静态代理的基本使用方法!

动态代理

动态代理是JDK为我们实现好的一种设计模式.动态代理和静态代理的最大区别就在于我们不需要自己去手写一个代理对象了,我们会创建一个代理对象工厂,而这个工厂可以按照我们的意愿来创造出我们需要的代理对象,从而完成代理的工作.
具体做法如下:
1.首先我们需要一个业务接口,里面规定必须要完成的业务

1
2
3
4
5
6
public interface IRealwork {
void create();
void delete();
void select();
void modify();
}

2.接下来我们要有一个主营业务类来继承这个接口,完成主要业务的业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class RealWork implements IRealwork {
public void create() {
System.out.println("增加");
}

public void delete() {
System.out.println("删除");
}

public void select() {
System.out.println("查找");
}

public void modify() {
System.out.println("修改");
}
}

3. 有了接口,有了主营类,现在就缺一个代理人了,静态代理中此时我们会自己手写一个代理类,然后实例化出代理对象,但在动态代理中不这样做了,我们创建一个工厂,这个工厂可以为我们提供任何我们所期望的代理对象,那么如何实现这个工厂,工厂里又都需要些什么东西呢?
1.首先我们的工厂类要继承接口InvocationHandler ,覆写里面的invoke方法.
2.需要能够返回一个代理对象给我们的方法
3.需要写入具体的代理业务
代理对象工厂大致就需要如上三个内容,具体代码实现如下:

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
public class ProxyFactory implements InvocationHandler {

//被代理对象
Object proxyDest = null;

public ProxyFactory(Object proxyDest) {
this.proxyDest = proxyDest;
}

//用来动态创建代理对象
public Object getProxyObject(){
return Proxy.newProxyInstance(
this.proxyDest.getClass().getClassLoader(),
this.proxyDest.getClass().getInterfaces(),
this
);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = null;
try {
this.safeCheck();
object = method.invoke(this.proxyDest, args);
} catch (Exception e) {
e.printStackTrace();
}

return object;
}

public void safeCheck() {
System.out.println("安全性检测");
}

}

代码解释:

  • 工厂类中我们需要把被代理对象传入(既然代理他,当然需要他)
  • getProxyObject方法: 这个方法是我们自己定义的一个动态的返回代理对象的方法,在这个方法中我们调用的JDK提供的Proxy类中的newProxyInstance方法,这个方法会在JVM内部动态的帮助我们创建一个我们需要的代理对象,但他怎么知道我们需要什么样的代理对象呢?这就要说说他的三个参数.
    ①.目标类(被代理类)的类加载器: 在创建代理对象的时候要用到被代理类的对象,想要将这个类加载到JVM中就要获得他的类加载器
    ②.目标类所实现的具体业务接口: 我理解的就是JVM需要知道我们的实际业务是什么
    ③.实现InvacationHandler接口的类的实例化对象即代理类对象,也就是当前类的对象 : JVM需要知道是通过那个工厂创建的这个代理类
    有了Proxy.newProxyInstance方法便能够创建出来任意一个满足我们要求的代理对象(前提是有接口,有目标对象)
  • invoke: 称之为回调函数,当这个类的实例化对象被创建的时候会自动调用该方法,这个方法中也有三个参数
    ①.proxy: 代表目标对象
    ②.method: 目标对象中的方法(真实的业务方法)
    ③.args: 目标对象中的方法
    在invoke函数里面我们需要添加我们的代理业务;然后使用参数method调用他里面覆写的invoke函数.传入被代理对象和被代理对象方法的中参数,这一步就相当于静态代理中在代理对象的业务中插入实际业务一样
  • safeCheck: 叫做横切关注点,暂可理解为代理业务

以上就是创建代理对象的工厂类的解释
测试类

1
2
3
4
5
6
7
public class TestDproxy {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(new RealWork()); //创建工厂,传入一个真实业务对象
IRealwork iRealwork = (IRealwork) proxyFactory.getProxyObject(); //使用工厂创建出来代理对象
iRealwork.create(); //使用代理对象调用业务
}
}

结果: 在具体业务增加的之前有了安全性检测

1
2
安全性检测
增加

Spring AOP

什么是AOP

AOP叫做面向切面编程.下面我将解释下什么是面向切面编程
假设我们的主体业务是CRUD,并且是一个纵向的结构,类似如下:

现在有一些辅助的业务,例如安全性检测,打印操作,他们需要插入到主题业务的某个业务之前或者之后,如图

注意观察: 此时这些辅助业务(safeCheck,printMsg)和实体业务(CRUD)是一种正交关系,正交关系有什么特点呢? 就是在辅助业务横向插入到实体业务中时,他会被顺序执行,但是如果我们不再需要这些辅助业务了,只需要横向的把他从竖向的实体业务中抽出就好,也不会影响到实体业务的执行,而这些辅助的业务我们就把它称之为横切关注点,由多个横切关注点组成的类,我们叫做切面类

面向切面编程(AOP)就是指将切面关注点集合为切面类,然后在程序主业务执行的过程中动态的将切面关注点插入到程序的某个位置
在AOP中有几个关键词,先以图的形式展示

文字解释: 在切面中有很多的横切关注点.每一个横切关注点就相当于一个辅助的业务,他们要插入到主体业务的某一个指定的位置.通知Advice用来告诉横切关注点什么时候插入,他有三个值,在某个方法之前插入使用before,之后插入使用after,出现异常时插入使用throw,通知常常修饰在横切关注点上.切点是用来规定横切关注点作用的范围的,例如本图中,如果让safeCheck只能作用在R查询的前面就要用pointcut来规定他只能作用于该方法,切点常常作用于通知上.当把横切关注点的插入时机,插入范围都规定好了之后确定的所要插入的这个点就叫做连接点,他是一个抽象的概念,并没有实体,然后整个插入的过程叫做织入.以上便是这些关键词的具体含义
以上就是AOP的核心概念,注意他是一种思想,并不是Java独有的东西,任何语言都可以实现这个思想

Spring AOP

Sping AOP是通过使用Java技术实现AOP思想构成的一套框架,能够更好的让我们实现业务的穿插,Spring AOP有两种实现方式,分别是注解方式和配置方式,以下将分别来介绍:

注解方式

首先我们要有主要的业务类CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IRealWork {
void create();
void delete();
void retrieve();
void update();
}

public class RealWork implements IRealWork {
public void create() {
System.out.println("增");
}

public void delete() {
System.out.println("删");
}

public void retrieve() {
System.out.println("查");
}

public void update() {
System.out.println("改");
}
}

准备切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Aspect
public class AspectTest {

@Pointcut("execution(* retr*(..))")
public void ponitCutTag1(){ }

@Pointcut("execution(* update(..))")
public void pointCutTag2(){}

@Before("ponitCutTag1()")
public void safeCheck(){
System.out.println("安全性检测");
}

@Before("pointCutTag2()")
public void printMsg(){
System.out.println("打印信息");
}
}

切面类注解说明:

  • @Adivce : 修饰于类名之上,表示这个类是一个切面类,如果没有此注解,那么这个类只是一个普通的Java类
  • @Pointcut: 切点,用于规定横切关注点的作用范围,括号里(“execution( retr(..))”)便是一种模糊定义,即前缀开头为retr的方法都会添加横切关注点
  • @Before/@After/@Throw: 都属于通知,规定横切关注点何时插入(方法前?方法后?出现异常时?)

这段代码就规定了safeCheck在retrieve方法执行之前插入.printMsg在update方法执行前插入
将切面类和业务类注册,交给Spring管理(通过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
24
25
26
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
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">

<!--加入如下标识标识Spring支持AOP-->
<aop:aspectj-autoproxy/>

<bean id="realWork" class="io.github.seevae.proxy.springaop.RealWork"/>
<bean id="myaspect" class="io.github.seevae.proxy.springaop.AspectTest"/>


<!--&lt;!&ndash;通过配置的方式来进行AOP的配置&ndash;&gt;-->

<!--<aop:config>-->
<!--<aop:aspect id="aspact1" ref="myAspect">-->
<!--<aop:pointcut id="mypointcut" expression="execution(* add*(..))"/>-->
<!--<aop:before method="safeCheck" pointcut-ref="mypointcut"/>-->
<!--</aop:aspect>-->
<!--</aop:config>-->

</beans>

测试代码

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IRealWork iRealWork = (IRealWork) applicationContext.getBean("realWork");
iRealWork.retrieve();
iRealWork.update();
}
}

此时在调用方法时会发现有了辅助业务,即横切关注点插入了进去
程序运行结果:

1
2
3
4
安全性检测

打印信息

配置文件方式

如果不能够使用注解进行开发,那么还有一种选择,就是通过配置文件的方式来实现Spring AOP
主要的业务类CRUD还是必不可少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IRealWork {
void create();
void delete();
void retrieve();
void update();
}

public class RealWork implements IRealWork {
public void create() {
System.out.println("增");
}

public void delete() {
System.out.println("删");
}

public void retrieve() {
System.out.println("查");
}

public void update() {
System.out.println("改");
}
}

此时切面类不再需要使用注解去配置,一个普通的类里面放有我们需要的辅助业务就好

1
2
3
4
5
6
7
public class AspectTest2 {

public void safeCheck(){
System.out.println("安全性检测");
}

}

有了主业务,有了辅助业务,接下来就是配置xml文件了:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--加入如下标识标识Spring支持AOP-->
<aop:aspectj-autoproxy/>
<!-- 在xml中我们需要将主业务,辅助业务,都进行配置交给Spring -->
<bean id="realWork" class="io.github.seevae.proxy.springaop.RealWork"/>
<bean id="myaspect2" class="io.github.seevae.proxy.springaop.AspectTest2"/>

<!-- 配置AOP关系-->
<aop:config>
<aop:aspect id="aspectTest2" ref="myaspect2">
<aop:pointcut id="mypointcut" expression="execution(* create(..))"/>
<aop:before method="safeCheck" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>

测试类

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IRealWork iRealWork = (IRealWork)applicationContext.getBean("realWork");
iRealWork.create();
}
}

结果:

1
2
安全性检测

成功添加安全性检测
这就是通过配置的方式来实现的Spring AOP

目前对Spring AOP以及代理模式还停留在表层阶段,比较浅显,日后深入学习后在进行改进叭~^_^