一、AOP的思想
都说AOP是OOP的一种升华,我觉得AOP实际上就是在OOP的基础上进行了一次封装,下面的图我觉得画的非常好。
图片转载于https://blog.csdn.net/q982151756/article/details/80513340
图中的贷款申请、贷款管理和入出金管理都包含相同的业务处理,我们把业务处理发生的位置叫做Join Point。假如我们的项目有上百个业务模块,那我们就需要写上百个Join Point,很显然这是不合理的。
我们可以写一个切面类(Aspect),将需要进行的业务(Advice)放在切面类中。现在我们将这些相同的业务处理单独拿了出来,代码的耦合度降低了,不需要重复写上百次,但是,我们怎么把它们放进各个业务模块里执行呢?
切点(Point Cut)可以解决这个问题,我们可以通过切入点表达式,定义我们需要进行的业务(Advice)将会在哪些Join Point执行。比如,我们需要在贷款申请和贷款管理中执行相同的业务,那么贷款申请和贷款管理的业务类本身,我们称为Target,将服务性代码和业务性代码动态结合的这个过程,我认为就是织入(Weaving)。
二、Spring是如何实现AOP的?
Spring使用代理来实现AOP,为什么需要使用代理?很简单,因为我们自己做不好,而我们的代理能帮我们做的很好,也就是说代理是对我们本身的一种增强。
AOP示例项目结构如下,删去了一些不必要的分支。
AOP包对应静态代理,即一个业务类对应一个切面类。
AOP2包对应动态代理,将切面类抽象出来,当我们需要使用切面类时进行调用即可。
IDE:IDEA 2019.1
JDK:1.8
├───src │ └───main │ ├───java │ │ ├───AOP │ │ │ FuWu.java │ │ │ MyInvocationHandler.java │ │ │ Test.java │ │ │ TestInterface.java │ │ │ YeWu.java │ │ │ │ │ └───AOP2 │ │ DaGuanSi.java │ │ LvShi.java │ │ MyInvocationHandler.java │ │ QuQi.java │ │ TestDaGuanSi.java │ │ │ ├───resource │ │ ApplicationContext.xml │ │ ApplicationContext2.xml │ │ log4j.properties
1、静态代理
这个示例项目中我写了两个Spring配置文件,一个是注解方式实现的0配置,一个是在xml中注册业务类、切面类以及使用<aop:aspect>进行织入。
ApplicationContext.xml
<?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/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<aop:aspectj-autoproxy proxy-target-class="false" />
<!--切面类-->
<bean id="fw" name="fuwu" class="AOP.FuWu"></bean>
<!--业务类-->
<bean id="yw" name="yewu" class="AOP.YeWu"></bean>
<!--织入-->
<aop:config>
<aop:aspect ref="fw">
<aop:before method="fun1" pointcut="execution(* AOP.YeWu.*(..))"></aop:before>
<aop:after method="fun2" pointcut="execution(* AOP.YeWu.*(..))"></aop:after>
<aop:around method="around" pointcut="execution(* AOP.YeWu.*(..))"></aop:around>
</aop:aspect>
</aop:config>
</beans>
ApplicationContext2.xml
<?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/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!--自动扫描AOP包下通过注解配置的组件-->
<context:component-scan base-package="AOP2"></context:component-scan>
<!--启用AspectJ注解的支持-->
<aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
比如我现在要打官司,那我就需要定义“我”这个类,并实现打官司这个接口。
“我”
package AOP2;
import org.springframework.stereotype.Component;
/**
* 我要打官司,所以我实现了打官司这个接口
*/
@Component
public class QuQi implements DaGuanSi {
@Override
public void souJiZhengJu() {
System.err.println("我自己搜集证据");
}
@Override
public void xieSuZhuang() {
System.err.println("我自己写诉状");
}
}
“打官司”接口
package AOP2;
/**
* 打官司接口
*/
public interface DaGuanSi {
//搜集证据
void souJiZhengJu();
//写诉状
void xieSuZhuang();
}
但我自己打官司打的并不好,由于没有法律知识,我的诉状并不规范,我搜集证据的能力并不足。所以我们需要“律师”来帮我们打官司。
package AOP2;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 我自己打官司打不赢,请律师来帮我打
*/
@Component
@Aspect
public class LvShi implements DaGuanSi {
@Override
@After(value = "execution(* AOP2.QuQi.souJiZhengJu(..))")
public void souJiZhengJu() {
System.err.println("律师帮我搜集证据");
}
@Override
@After(value = "execution(* AOP2.QuQi.xieSuZhuang(..))")
public void xieSuZhuang() {
System.err.println("律师帮我写诉状");
}
}
那我们来写一个测试类,看看律师帮我打官司打的如何了。
package AOP2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.lang.reflect.Proxy;
/**
* 打官司测试类
*/
public class TestDaGuanSi {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext2.xml");
staticProxy(ac);
}
/**
* 抽象后的织入
*/
private static void dynamicProxy() {
//创建一个打官司的人
QuQi me = new QuQi();
//获取类
Class c = me.getClass();
//获取类加载器
ClassLoader cl = c.getClassLoader();
//获取类实现的接口
Class[] interfaces = c.getInterfaces();
//创建一个代理类的对象,和被代理对象关联起来,其实就是我要去打官司,我现在就是在找律师
MyInvocationHandler mih = new MyInvocationHandler(me);
//获取指定接口的代理类实例
DaGuanSi newProxy = (DaGuanSi) Proxy.newProxyInstance(cl, interfaces, mih);
newProxy.xieSuZhuang();
newProxy.souJiZhengJu();
System.err.println(newProxy.getClass());
}
/**
* 传统的织入
* @param ac
*/
private static void staticProxy(ApplicationContext ac) {
//这里由于我们使用注解配置,不能像之前ac.getBean("yw")那样获取Bean了,所以这里只能使用cglib代理方式
QuQi me = ac.getBean(QuQi.class);
me.souJiZhengJu();
me.xieSuZhuang();
System.err.println(me.getClass().getName());
}
}
测试结果如下:
从结果可以看到,我们的切面类已经成功织入业务类。代理有JDK代理和cglib代理,这里我们使用的cglib代理。
那我们想一想,如果还有其他人不擅长打官司找律师帮忙呢?那我们就要为其他人专门写新的律师类,很显然这是不合理的。
动态代理就是帮助我们解决这个问题的,我们专门为代理写一个MyInvocationHandler类,该类实现InvocationHandler接口。
比如这次C要来打官司,那我们就调用这个类,同时通过构造方法将C这个人传入这个代理类,和被代理对象关联起来。
在测试类中执行一下,结果如下:
那下次别人再想要打官司,他们就只需要找我们抽象出来的这个律师就可以了,流程更加合理。