前言
AOP(Aspect Oriented Programming)即面向切面编程,是一种通过预编译方式和运行期间动态代理实现程序功能统一维护的技术。针对功能增强的描述,可以理解为:“AOP允许在不修改源代码的情况下,通过定义切面(Aspect)和切入点(Pointcut),对目标对象的方法进行功能增强。”
具体来说,AOP能够将业务逻辑中的横切关注点(如日志、安全、事务等)与核心业务逻辑分离,从而降低代码间的耦合度,提高程序的可重用性和开发效率。在Spring框架中,AOP提供了多种增强类型,如前置增强、后置增强、环绕增强、异常抛出增强和引介增强等,这些增强类型可以灵活应用于不同的业务场景,以满足不同的功能需求。
案例分析
实现手机购买和售卖的案例,利用AOP添加日志功能,显示当前时间等信息。
案例实现
第一步:创建maven项目,导入相关依赖
pom.xml
这是spring3.0使用AOP的相关依赖,如果使用的是spring5.0以上,导包可能没有这么多,这是我的spring3.0 AOP的最佳实践
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>3.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.6.11</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>3.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.6.11</version>
    </dependency>
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.1</version>
    </dependency>
第二步:创建service层业务,创建接口和实现类完成基本功能
 
PhoneBiz
public interface PhoneBiz {
    void buyPhone(int num);
    void salePhone(int num) throws OutOfStackException;
}
PhoneBizImpl
public class PhoneBizImpl implements PhoneBiz {
    int num;  //库存
    @Override
    public void buyPhone(int num) {
        System.out.println("手机进货,数量为"+num+"部");
        this.num+=num;
    }
    @Override
    public void salePhone(int num) throws OutOfStackException {
     
        System.out.println("手机销售,数量为"+num+"部");
    }
}
第三步:创建LogAspect切面类和spring的配置文件spring.xml
LogAspect
完成前置通知,在原始方法执行前进行日志输出
public class LogAspect {
    public String currTime(){
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        return sdf.format(new Date());
    }
    /**
     * 前置通知
     * @param jp
     */
    public void before(JoinPoint jp){
        //获得请求方法的参数
        Object[] args= jp.getArgs();
        //获得请求方法名
        String methodName= jp.getSignature().getName();
        if ("buyPhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",即将开始进货操作,数量为"+args[0]+"部");
        }
        if ("salePhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",即将开始销售操作,数量为"+args[0]+"部");
        }
    }spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       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-3.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <bean id="phoneBiz" class="com.by.service.PhoneBizImpl"></bean>
    <bean id="logAspectBean" class="com.by.util.LogAspect"></bean>
    <aop:config proxy-target-class="true">
        <aop:pointcut id="p1" expression="execution( void *Phone(int))"/>
        <aop:aspect id="logAspect" ref="logAspectBean">
            <aop:before method="before" pointcut-ref="p1"/>
        </aop:aspect>
    </aop:config>
</beans>第四步:分别完成最终通知,后置通知,异常通知,环绕通知
LogAspect
 /**
     * 后置通知
     * @param jp
     */
    public void afterReturing(JoinPoint jp){
        //获得请求方法的参数
        Object[] args= jp.getArgs();
        //获得请求方法名
        String methodName= jp.getSignature().getName();
        if ("buyPhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",进货操作结束,数量为"+args[0]+"部");
        }
        if ("salePhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",销售操作结束,数量为"+args[0]+"部");
        }
    }
    /**
     * 异常通知
     * @param jp
     * @param e
     */
    public void afterThrowing(JoinPoint jp,OutOfStackException e){
        String methodName= jp.getSignature().getName();
        System.out.println(currTime()+":"+methodName+"发生异常:"+e.getMessage());
    }
    /**
     * 后置通知,发生异常也会执行
     * @param jp
     */
    public void after(JoinPoint jp){
        String methodName= jp.getSignature().getName();
        if ("buyPhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",进货操作完毕,发生异常也会执行");
        }
        if ("salePhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",销售操作完毕,发生异常也会执行");
        }
    }
    /**
     * 环绕通知
     * @param pjp
     * @return
     * @throws Throwable
     */
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        String methodName=  pjp.getSignature().getName();
        System.out.println(methodName+"方法开始执行:");
        long begin= System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            long end= System.currentTimeMillis();
            System.out.println(methodName+"方法执行完毕,耗时"+(end-begin)+"毫秒");
        }
    }
    public String currTime(){
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        return sdf.format(new Date());
    }spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       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-3.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <bean id="phoneBiz" class="com.by.service.PhoneBizImpl"></bean>
    <bean id="logAspectBean" class="com.by.util.LogAspect"></bean>
    <aop:config proxy-target-class="true">
        <aop:pointcut id="p1" expression="execution( void *Phone(int))"/>
        <aop:aspect id="logAspect" ref="logAspectBean">
            <aop:before method="before" pointcut-ref="p1"/>
            <aop:after-returning method="afterReturing" pointcut-ref="p1"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="p1" throwing="e"/>
            <aop:after method="after" pointcut-ref="p1"/>
            <aop:around method="around" pointcut-ref="p1"/>
        </aop:aspect>
    </aop:config>
</beans>注意:如果要完成异常通知,需要预先自定义一个异常类。
package com.by.util;
/**
 * 库存异常类
 */
public class OutOfStackException extends Exception {
    public OutOfStackException(String message) {
        super(message);
    }
}
在PhoneBizImpl中新增判断语句
  @Override
    public void salePhone(int num) throws OutOfStackException {
        if (this.num<num){
            throw  new OutOfStackException("库存不足,需要"+num+"部,只有"+this.num+"部");
        }
        System.out.println("手机销售,数量为"+num+"部");
    }
第五步:将配置文件配置AOP的操作改为注解实现
将spring.xml中的配置注解掉,开启注解注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       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-3.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <context:component-scan base-package="com.by"/>
    <!--启用注解aop-->
    <aop:aspectj-autoproxy/>
<!--    <bean id="phoneBiz" class="com.by.service.PhoneBizImpl"></bean>-->
<!--    <bean id="logAspectBean" class="com.by.util.LogAspect"></bean>-->
<!--    <aop:config proxy-target-class="true">-->
<!--        <aop:pointcut id="p1" expression="execution( void *Phone(int))"/>-->
<!--        <aop:aspect id="logAspect" ref="logAspectBean">-->
<!--            <aop:before method="before" pointcut-ref="p1"/>-->
<!--            <aop:after-returning method="afterReturing" pointcut-ref="p1"/>-->
<!--            <aop:after-throwing method="afterThrowing" pointcut-ref="p1" throwing="e"/>-->
<!--            <aop:after method="after" pointcut-ref="p1"/>-->
<!--            <aop:around method="around" pointcut-ref="p1"/>-->
<!--        </aop:aspect>-->
<!--    </aop:config>-->
</beans>在PhoneBizImpl实现类中:
@Component(value = "phoneBiz")
public class PhoneBizImpl implements PhoneBiz {
...}在LogAspect中使用注解:
/**
 * 通知类
 */
@Aspect
@Component
public class LogAspect {
    /**
     * 前置通知
     * @param jp
     */
    @Before("p1()")
    public void before(JoinPoint jp){
        //获得请求方法的参数
        Object[] args= jp.getArgs();
        //获得请求方法名
        String methodName= jp.getSignature().getName();
        if ("buyPhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",即将开始进货操作,数量为"+args[0]+"部");
        }
        if ("salePhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",即将开始销售操作,数量为"+args[0]+"部");
        }
    }
    /**
     * 后置通知
     * @param jp
     */
    @AfterReturning("execution( void *Phone(int))")
    public void afterReturing(JoinPoint jp){
        //获得请求方法的参数
        Object[] args= jp.getArgs();
        //获得请求方法名
        String methodName= jp.getSignature().getName();
        if ("buyPhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",进货操作结束,数量为"+args[0]+"部");
        }
        if ("salePhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",销售操作结束,数量为"+args[0]+"部");
        }
    }
    /**
     * 异常通知
     * @param jp
     * @param e
     */
    @AfterThrowing(pointcut = "execution( void *Phone(int))",throwing = "e")
    public void afterThrowing(JoinPoint jp,OutOfStackException e){
        String methodName= jp.getSignature().getName();
        System.out.println(currTime()+":"+methodName+"发生异常:"+e.getMessage());
    }
    /**
     * 后置通知,发生异常也会执行
     * @param jp
     */
    @After("execution( void *Phone(int))")
    public void after(JoinPoint jp){
        String methodName= jp.getSignature().getName();
        if ("buyPhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",进货操作完毕,发生异常也会执行");
        }
        if ("salePhone".equals(methodName)){
            System.out.println("日志:"+currTime()+",销售操作完毕,发生异常也会执行");
        }
    }
    /**
     * 环绕通知
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("execution( void *Phone(int))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        String methodName=  pjp.getSignature().getName();
        System.out.println(methodName+"方法开始执行:");
        long begin= System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            long end= System.currentTimeMillis();
            System.out.println(methodName+"方法执行完毕,耗时"+(end-begin)+"毫秒");
        }
    }
    @Pointcut("execution( void *Phone(int))")
    public void p1(){}
    public String currTime(){
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        return sdf.format(new Date());
    }
}
总结
以上是基于spring3.0使用AOP实现日志记录功能的小案例



















