一、什么是AOP
1. 简介
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理 实现程序功能的统一维护的一种技术。
AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍 生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序 的可重用性,同时提高了开发的效率
2. 作用及优势
- 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 优势:减少重复代码,提高开发效率,并且便于维护
3. 底层实现原理
spring提供的一种动态代理技术实现功能的增强
(1)JDK代理:基于接口的动态代理技术
我们先来写一个简单的目标接口:
public interface TargetInterface {
public void save();
}
我们再写一个目标对象,实现刚才那个目标接口:
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("save....");
}
}
最后我们实现一个动态代理方法使其功能增强,比如前置增强和后置增强。
public class ProxyTest {
public static void main(String[] args) {
//目标对象
Target target = new Target();
TargetInterface tagetInterface = (TargetInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强...."); //前置增强
Object invoke = method.invoke(target, args); //执行目标方法
System.out.println("后置增强...."); //后置增强
return invoke;
}
});
//调用代理对象的方法
tagetInterface.save();
}
}
查看运行结果可以看到方法被增强了!
(2)cglib代理:基于父类的动态代理技术
我们还是先写一个简单的目标对象
public class Target {
public void save() {
System.out.println("save running.....");
}
}
然后实现动态代理方法实现功能增强
public class ProxyTest {
public static void main(String[] args) {
//目标对象
final Target target = new Target();
//1、创建增强器
Enhancer enhancer = new Enhancer();
//2、设置父类(目标)
enhancer.setSuperclass(Target.class);
//3、设置回调
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("前置增强....");
Object invoke = method.invoke(target, args); //执行目标
System.out.println("后置增强....");
return invoke; //返回值 就是动态生成的代理对象 基于cglib
}
});
//4、创建代理对象
Target proxy = (Target) enhancer.create();
proxy.save();
}
}
查看运行结果可以看到方法被同样增强了!
二、基于XML的AOP开发
1. 导入AOP相关坐标
找到 pom.xml,将下面两个坐标导入
(1)spring的context坐标,context依赖aop
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
(2)aspectj的织入
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
2. 创建类和方法
(1)目标接口
public interface TargetInterface {
public void save();
}
(2)目标类(也就是切点)
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("save....");
}
}
(3) 切面类(也就是增强方法)
public class InvokeDemo {
public void before(){
System.out.println("前置增强....");
}
public void afterReturning(){
System.out.println("后置增强....");
}
}
3. 面向切面编程
(1)将目标类和切面类的对象创建权交给 spring
找到 applicationContext.xml,写入以下代码:
<!--配置目标类-->
<bean id="target" class="com.itheima.aop.Target"></bean>
<!--配置切面类-->
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>
(2)在 applicationContext.xml 中配置织入关系,
首先导入aop命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
">
然后配置切点表达式和前置增强的织入关系:
先写出大体的格式:
<aop:config>
<aop:aspect ref=“切面类”>
<aop:before method=“通知方法名称” pointcut=“切点表达式"></aop:before>
</aop:aspect>
</aop:config>
注意这里的切点表达式的写法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号* 代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
- 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
通知类型:
切点表达式的抽取:
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式 。
比如下面的完整写法中,把切点命名为 myPointCut 并放入 <aop:aspect ... /> 标签中,直接再下面的前置通知或后置通知 pointcut-ref="myPointCut" 调用即可。
最后,完整写出配置切点表达式和前置增强的织入关系:
<!--配置织入-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<aop:pointcut id="myPointCut" expression="execution(* aop.*.*(..))"/>
<!--切面:切点+通知-->
<aop:before method="before" pointcut-ref="myPointCut"/>
<!--<aop:after method="afterReturning" pointcut="execution(public void aop.Target.save())"/>-->
<aop:after-returning method="afterReturning" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>-->
4. 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
运行结果:
这样我们就实现了基于XML的AOP开发!不过我们一般使用更为方便的注解开发。
三、基于注解的 AOP 开发
1. 导入AOP相关坐标
与XML开发完全相同
2. 创建类和方法
(1)创建目标接口和目标类(与XML开发相同)
// 目标接口
public interface TargetInterface {
public void save();
}
// 目标类(也就是切点)
// @Component("target")
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("save....");
}
}
注意这里的切点类也可以使用下面说到的的 @Component() 注解,不加也行!
(3) 切面类
首先我们需要用到 @Component() @Pointcut() @Aspect() 注解
- @Component():实现bean的注入
- @Pointcut():抽取切面表达式
- @Aspect():表明当前类是一个切面类
@Component("myAspect")
@Aspect //标注切面类
public class MyAspect {
@Pointcut("execution(* aop.*.*(..))") // 抽取切面表达式
public void pointCut(){};
@Before("MyAspect.pointCut()")
public void before(){
System.out.println("前置增强....");
}
@AfterReturning("MyAspect.pointCut()")
public void afterReturning(){
System.out.println("后置增强....");
}
}
然后通知方法的注解:
3. 在配置文件中开启组件扫描和 AOP 的自动代理
打开 applicationContext.xml 文件,将基于 xml 的AOP开发加注释,然后加上以下两行配置。
base-package 的值是需要扫描的切面和切点类所在的package包。
<!--组件扫描-->
<context:component-scan base-package="aop"/>
<!--aop自动代理-->
<aop:aspectj-autoproxy/>
4. 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
编写测试代码,可以看到也能实现方法增强。