Spring AOP技术(基于AspectJ)的XML开发

Spring AOP技术(基于AspectJ)的XML开发

@(Spring)[aop, spring, xml, Spring, annotation, aspectJ]

Spring AOP的XML的开发

AOP的概述

什么是AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

从最开始的面向机器编程到面向过程编程到面向对象对象编程直至现在的面向切面编程可以看出,随着计算机软件的不断发展,其越来越符合人的思维习惯,同样的代码量所能实现的功能也越来越多。而AOP则是在不增业务逻辑的代码上,增加新功能。而AOP一般用于框架开发。在实际项目中,使用AOP也是一个趋势。

AOP面向切面编程,是OOP扩展和延伸,使用的是横向抽取机制取代传统的纵向继承体系对程序进行扩展。解决OOP中遇到问题。

Spring中的AOP

Spring的AOP的底层实现
  • Spring提供两种代理机制实现AOP
    • JDK动态代理 :JDK只能对实现了接口的类产生代理。
    • CGLIB动态代理 :可以对没有实现接口的类产生代理,用的是底层字节码增强技术。产生类继承父类。
Spring的底层实现之JDK动态代理
package com.pc.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK动态代理实现 
 * 需要提供类的接口
 * 
 * @author Switch
 * @data 2016年11月23日
 * @version V1.0
 */
public class JDKProxy<T> implements InvocationHandler {
    // 持有需要被增强的对象的引用
    T target;

    /**
     * 构造方法必须提供被代理对象
     * 
     * @param target
     */
    public JDKProxy(T target) {
        this.target = target;
    }

    /**
     * 创建代理对象
     * 
     * @return
     */
    public T createProxy() {
        T proxy = (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                this);
        return proxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强,这里实现前置通知
        System.out.println("前置通知。。。。。");
        // 调用原有接口方法
        return method.invoke(target, args);
    }
}
package com.pc.test;

import org.junit.Test;

import com.pc.aop.JDKProxy;
import com.pc.service.UserService;
import com.pc.service.impl.UserServiceImpl;

/**
 * 代理测试类
 * 
 * @author Switch
 * @data 2016年11月23日
 * @version V1.0
 */
public class ProxyTest {
    @Test
    public void testJDKProxy() {
        // 创建被代理对象
        UserService userService = new UserServiceImpl();
        // 未增强之前的方法
        // 输出:保存用户
        userService.save();
        JDKProxy<UserService> jdkProxy = new JDKProxy<>(userService);
        // 创建代理对象
        userService = jdkProxy.createProxy();
        // 增强后的方法
        // 输出:
        // 前置通知。。。。。
        // 保存用户
        userService.save();
    }
}
Spring的底层实现之CGLIB动态代理
package com.pc.aop;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

/**
 * CGLIB实现代理 
 * 使用字节码增强技术,生成代理类的子类
 * 
 * @author Switch
 * @data 2016年11月23日
 * @version V1.0
 */
public class CGLIBProxy<T> implements MethodInterceptor {
    // 持有需要被增强的对象的引用
    T target;

    /**
     * 构造方法必须提供被代理对象
     * 
     * @param target
     */
    public CGLIBProxy(T target) {
        this.target = target;
    }

    /**
     * 创建代理对象
     * @return
     */
    public T createProxy() {
        // 创建代理核心对象
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调
        enhancer.setCallback(this);
        // 创建代理对象
        T proxy = (T) enhancer.create();
        return proxy;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 增强,这里实现后置通知
        // 调用原有类方法,获得返回值
        Object result = methodProxy.invokeSuper(proxy, args);
        // 增强
        System.out.println("后置通知。。。。。");
        return result;
    }

}
package com.pc.test;

import org.junit.Test;

import com.pc.aop.CGLIBProxy;
import com.pc.service.UserService;
import com.pc.service.impl.UserServiceImpl;

/**
 * 代理测试类
 * 
 * @author Switch
 * @data 2016年11月23日
 * @version V1.0
 */
public class ProxyTest {
    @Test
    public void testCGLIBProxy() {
        // 创建被代理对象
        UserService userService = new UserServiceImpl();
        // 未增强之前的方法
        // 输出:保存用户
        userService.save();
        CGLIBProxy<UserService> cglibProxy = new CGLIBProxy<>(userService);
        // 创建代理对象
        userService = cglibProxy.createProxy();
        // 增强后的方法
        // 输出:
        // 保存用户
        // 后置通知。。。。。
        userService.save();
    }
}

Spring的AOP的术语

Spring的AOP开发分成两类
  • 传统AOP开发
  • 基于AspectJ的AOP的开发(XML和注解)
AOP开发中的术语

这里写图片描述

  1. 切面(aspect):要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。
  2. 连接点(join point):应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。
  3. 通知(advice):切面的实际实现,它通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。
  4. 切入点(pointcut):定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。
  5. 引介(introduction):为类添加新方法和属性。
  6. 目标对象(target):被通知的对象。既可以是自己编写的类也可以是第三方类。
  7. 代理(proxy):将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
  8. 织入(Weaving):将切面应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:
    编译期:切面在目标对象编译时织入。这需要一个特殊的编译器。
    类装载期:切面在目标对象被载入JVM时织入。这需要一个特殊的类载入器。
    运行期:切面在应用系统运行时织入。不需要特殊的编译器。

PS:spring只支持方法连接点,不提供属性接入点,spring的观点是属性拦截破坏了封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。

Spring的基于AspectJ的AOP的XML开发

第一步引入jar包

这里写图片描述

第二步创建配置文件
  • 引入AOP的约束
<beans xmlns="http://www.springframework.org/schema/beans"
    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.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd"> 

</beans>
第三步创建需增强包和类
  • 创建接口
package com.pc.aop.dao;

/**
 * 客户持久层接口
 * 
 * @author Switch
 * @data 2016年11月23日
 * @version V1.0
 */
public interface CustomerDao {
    /**
     * 保存用户
     */
    public void save();
}
  • 创建实现类
package com.pc.aop.dao.impl;

import com.pc.aop.dao.CustomerDao;

/**
 * 用户持久层实现类
 * 
 * @author Switch
 * @data 2016年11月23日
 * @version V1.0
 */
public class CustomerDaoImpl implements CustomerDao {
    @Override
    public void save() {
        System.out.println("保存用户了。。。");
    }
}
第四步将类交给Spring管理
<bean id="customerDao" class="com.pc.aop.dao.impl.CustomerDaoImpl" />
第五步编写测试
package com.pc.test;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.pc.aop.dao.CustomerDao;

/**
 * 面向切面编程测试类
 * 
 * @author Switch
 * @data 2016年11月23日
 * @version V1.0
 */
// 配置Spring单元测试环境
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringAOPTest {
    // 注入依赖
    @Resource(name = "customerDao")
    private CustomerDao customerDao;

    // 测试Spring单元测试集成
    @Test
    public void testSpring() {
        customerDao.save();
    }
}

输出

保存用户了。。。

PS:这时候没用使用AOP进行任何增强

第六步编写切面类和通知
package com.pc.aop.advice;
import org.aspectj.lang.ProceedingJoinPoint;
/**
 * 切面类
 * 
 * @author Switch
 * @data 2016年11月23日
 * @version V1.0
 */
public class MyAspect {
    // 通知:校验权限
    public void check() {
        System.out.println("校验权限。。。。。。");
    }
}
第七步 配置切面类
<!-- 配置切面类 -->
<bean id="myAspect" class="com.pc.aop.advice.MyAspect"/>
第八步通过配置实现AOP
<!-- 面向切面配置,基于aspectJ -->
<aop:config>
    <!-- 配置切入点 ,切入点是通过表达式来实现的-->
    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>
    <!-- 配置切面,切面是由具体的切入点和通知组成的 -->
    <aop:aspect ref="myAspect">
        <!-- 增强:前置通知 -->
        <aop:before method="check" pointcut-ref="pointcut1"/>
    </aop:aspect>
</aop:config>
第九步执行测试
package com.pc.test;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.pc.aop.dao.CustomerDao;

/**
 * 面向切面编程测试类
 * 
 * @author Switch
 * @data 2016年11月23日
 * @version V1.0
 */
// 配置Spring单元测试环境
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringAOPTest {
    // 注入依赖
    @Resource(name = "customerDao")
    private CustomerDao customerDao;

    // 测试Spring单元测试集成
    @Test
    public void testSpring() {
        customerDao.save();
    }
}

输出

校验权限。。。。。。
保存用户了。。。

切入点表达式的定义

切入点表达式定义语法
  • 切入点表达式基于execution之类的方法来实现的。在方法内部就可以编写切入点表达式:
    • 表达式语法:
[方法访问修饰符] 方法返回值 [包名.类名.]方法名(参数) [异常类型]

这里写图片描述

举例:

// 所有的public方法
public * *(..)
// 所有service中的public方法
public * com.pc.service.*.*(..)
// 所有以find开头的方法
* find*(..)
// public修饰符,void返回类型,全路径匹配
public void com.pc.aop.dao.impl.CustomerDaoImpl.save(..)
// 任意返回类型,全路径匹配
* com.pc.aop.dao.impl.CustomerDaoImpl.save(..)
// 任意返回类型,类名以Dao结尾下的任意方法
* com.pc.dao.*Dao.*(..)
// 任意返回类型,CustomerDao及其子类下的任意方法
* com.pc.dao.CustomerDao+.*(..)
// 任意返回类型,com.pc.dao包下的任意方法
* com.pc.dao..*.*(..)

通知类型

这里写图片描述

前置通知

前置通知:在目标方法执行之前完成的增强。获得切入点信息。
PS:接入点信息,其他类型的增强也可以通过这种方法使用。

创建需增强类和方法
public class CustomerDaoImpl implements CustomerDao {
    @Override
    public void save() {
        System.out.println("保存用户了。。。");
    }
}
创建切面和通知
public class MyAspect {
    // 通知:校验权限
    public void check(JoinPoint joinPoint) {
        System.out.println("校验权限。。。。。。");
        // 输出接入点信息
        System.out.println(joinPoint.toString());
    }
}
配置切面和通知
<!-- 配置切面类 -->
<bean id="myAspect" class="com.pc.aop.advice.MyAspect"/>

<!-- 面向切面配置,基于aspectJ -->
<aop:config>
    <!-- 配置切入点 ,切入点是通过表达式来实现的-->
    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>
    <!-- 配置切面,切面是由具体的切入点和通知组成的 -->
    <aop:aspect ref="myAspect">
        <!-- 增强:前置通知 -->
        <aop:before method="check" pointcut-ref="pointcut1"/>
    </aop:aspect>
</aop:config>
单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringAOPTest {
    // 注入依赖
    @Resource(name = "customerDao")
    private CustomerDao customerDao;
    // 测试前置通知
    @Test
    public void testBefore() {
        customerDao.save();
    }
}

输出

校验权限。。。。。。
execution(void com.pc.aop.dao.CustomerDao.save())
保存用户了。。。
后置通知

后置通知:在目标方法执行之后完成的增强。获得方法的返回值。

创建需增强类和方法
public class CustomerDaoImpl implements CustomerDao {
    @Override
    public String delete() {
        System.out.println("删除用户了。。。");
        return "delete";
    }
}
创建切面和通知
public class MyAspect {
    // 通知:打印日志
    public void printLog(String retVal) {
        System.out.println("打印日志。。。。。");
        System.out.println("返回值为:" + retVal);
    }
}
配置切面和通知
<!-- 配置切面类 -->
<bean id="myAspect" class="com.pc.aop.advice.MyAspect"/>

<!-- 面向切面配置,基于aspectJ -->
<aop:config>
    <!-- 配置切入点 ,切入点是通过表达式来实现的-->
    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.*.delete(..))" id="pointcut2"/>
    <!-- 配置切面,切面是由具体的切入点和通知组成的 -->
    <aop:aspect ref="myAspect">
        <!-- 增强:后置通知 -->
        <aop:after-returning method="printLog" pointcut-ref="pointcut2" returning="retVal"/>
    </aop:aspect>
</aop:config>
单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringAOPTest {
    // 注入依赖
    @Resource(name = "customerDao")
    private CustomerDao customerDao;
    // 测试后置通知
    @Test
    public void testAfterRunning() {
        String delete = customerDao.delete();
        System.out.println(delete);
    }
}

输出

删除用户了。。。
打印日志。。。。。
返回值为:delete
delete
环绕通知

环绕通知:在目标方法执行前和执行后完成的增强。阻止目标方法的执行,获得方法参数。

创建需增强类和方法
public class CustomerDaoImpl implements CustomerDao {
    @Override
    public void update(Integer id) {
        System.out.println("更新用户了。。。");
    }
}
创建切面和通知
public class MyAspect {
    // 通知:计算方法耗时
    public void calTime(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("方法执行前。。。。。。");
        // 打印参数
        System.out.println("参数为:" + joinPoint.getArgs()[0]);
        // 执行目标方法
        joinPoint.proceed();
        System.out.println("方法执行后。。。。。。");
    }
}
配置切面和通知
<!-- 配置切面类 -->
<bean id="myAspect" class="com.pc.aop.advice.MyAspect"/>

<!-- 面向切面配置,基于aspectJ -->
<aop:config>
    <!-- 配置切入点 ,切入点是通过表达式来实现的-->
    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.*.update(..))" id="pointcut3"/>
    <!-- 配置切面,切面是由具体的切入点和通知组成的 -->
    <aop:aspect ref="myAspect">
        <!-- 增强:环绕通知 -->
        <aop:around method="calTime" pointcut-ref="pointcut3"/>
    </aop:aspect>
</aop:config>
单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringAOPTest {
    // 注入依赖
    @Resource(name = "customerDao")
    private CustomerDao customerDao;
    // 测试环绕通知
    @Test
    public void testAround() {
        customerDao.update(6166);
    }
}

输出

方法执行前。。。。。。
参数为:6166
更新用户了。。。
方法执行后。。。。。。
异常抛出通知

异常抛出通知:在目标方法执行出现异常的时候完成的增强。获得异常的信息。

创建需增强类和方法
public class CustomerDaoImpl implements CustomerDao {
    @Override
    public void find() {
        System.out.println("查询用户了。。。");
        int i = 1 / 0;
    }
}
创建切面和通知
public class MyAspect {
    // 通知:异常处理
    public void throwHandler(Throwable ex) {
        System.out.println("异常处理。。。。。。" + ex.getMessage());
    }
}
配置切面和通知
<!-- 配置切面类 -->
<bean id="myAspect" class="com.pc.aop.advice.MyAspect"/>

<!-- 面向切面配置,基于aspectJ -->
<aop:config>
    <!-- 配置切入点 ,切入点是通过表达式来实现的-->
    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.*.find())" id="pointcut4"/>
    <!-- 配置切面,切面是由具体的切入点和通知组成的 -->
    <aop:aspect ref="myAspect">
        <!-- 增强:异常通知 -->
        <aop:after-throwing method="throwHandler" pointcut-ref="pointcut4" throwing="ex"/>
    </aop:aspect>
</aop:config>
单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringAOPTest {
    // 注入依赖
    @Resource(name = "customerDao")
    private CustomerDao customerDao;
    // 测试异常通知
    @Test
    public void testAfterThrowing() {
        customerDao.find();
    }
}

输出

查询用户了。。。
异常处理。。。。。。/ by zero
最终通知

最终通知:无论目标方法是否出现异常总是执行的增强。

PS:该案例和异常测试案例对同一个target进行增强。

创建需增强类和方法
public class CustomerDaoImpl implements CustomerDao {
    @Override
    public void find() {
        System.out.println("查询用户了。。。");
        int i = 1 / 0;
    }
}
创建切面和通知
public class MyAspect {
    // 通知:关闭资源
    public void close() {
        System.out.println("关闭资源。。。。。。");
    }
}
配置切面和通知
<!-- 配置切面类 -->
<bean id="myAspect" class="com.pc.aop.advice.MyAspect"/>

<!-- 面向切面配置,基于aspectJ -->
<aop:config>
    <!-- 配置切入点 ,切入点是通过表达式来实现的-->
    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.*.find())" id="pointcut4"/>
    <!-- 配置切面,切面是由具体的切入点和通知组成的 -->
    <aop:aspect ref="myAspect">
        <!-- 增强:最终通知 -->
        <aop:after method="close" pointcut-ref="pointcut4"/>
    </aop:aspect>
</aop:config>
单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringAOPTest {
    // 注入依赖
    @Resource(name = "customerDao")
    private CustomerDao customerDao;
    // 测试最终通知
    @Test
    public void testFinally() {
        customerDao.find();
    }
}

输出

查询用户了。。。
关闭资源。。。。。。
异常处理。。。。。。/ by zero

PS:之后会在《Spring AOP技术(基于AspectJ)的Annotation开发中》中会介绍怎么使用注解进行AOP开发,用注解进行AOP开发相对于XML来说更便捷,也更容易理解。这两种掌握一种就可以了,但是如果要对AOP进行集中管理,最好还是使用XML方式。

Spring AOP小项目

GitHub:Spring AOP小项目
GitHub:MyStore-netease

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值