Spring+AspectJ框架使用实践

在实际开发中对可能会在现场使用中出错的地方加上log日志,但随着现场的增加、上线应用时长的增加以及开发需求的添加,使得日志代码量不断增加,业务代码和非业务代码严重耦合。如果下面的测试示例代码块所示,为了精确判定出问题的函数,有时需要在函数开始、结束以及重要代码块中添加日志进行跟踪确认:
public class TestServiceImpl implements TestService{
    protected final Log logger = LogFactory.getLog(getClass());

    @Override
    public void eatCarrot() {
        logger.info("eatCarrot start ! ");
        System.out.println("吃萝卜");
        logger.info("eatCarrot end ! ");
    }

    @Override
    public void eatMushroom() {
        logger.info("eatMushroom start ! ");
        System.out.println("吃蘑菇");
        logger.info("eatMushroom start ! ");
    }

    @Override
    public void eatCabbage() {
        logger.info("eatCabbage start ! ");
        System.out.println("吃白菜");
        logger.info("eatCabbage start ! ");
    }
}
除此之外,如果再需要统计函数的执行时间等操作则需要增加更多的冗余、与业务代码耦合的程序代码。但其实Spring+AspectJ框架已有更为方便的处理方式了。采用AOP切面的方式拦截进入函数的调用,并在函数执行前后中进行相关操作。下面将根据项目开发中进行的改进,并以自己编写的测试示例进行展开。
对于未实现接口的类中的方法(如下面示例中WorkServiceImpl类中的qualityControl()函数)或在接口实现类的函数中直接调用该接口实现类的另一个函数(如:WorkServiceImpl类中的developProgram()函数直接调用devOps()函数),默认是不会走代理,而是直接使用该类的隐式this对象进行自调用的,即AOP切面是不会对其进行拦截的,除非强制使用代理。
1、Aspect的execution表达式
execution()函数是最常用的AOP切点函数,语法如下所示:
execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
execution()函数常用如下图所示:

* :匹配任意数量的字符;
..:匹配任意数量的字符,在类型模式中匹配任意数量子包(aop..service.WorkService);而在方法参数模式中匹配任意数量的参数(如上图所示)。
+ : 匹配指定类型的子类型(比如EmployeeService接口继承WorkService接口,WorkService+包含        
    EmployeeService接口;再比如WorkServiceImpl文件中的qualityControl()是没有实现接口的函数,但在使用强制代理的情况下也会被检测到)。仅能作为后缀放在类型模式后边。
execution示例如下:
1) 通过方法名定义AOP切点:
 execution(public * *(..))    : 匹配所有目标类中的pulbic方法,其中第一个*表示方法的返回值类型为任意类型,第二个*表示方法为任意方法名为任意名称。
 execution(* Ops(..))         : 匹配所有目标类中以Ops为后缀的方法名称。
2) 通过类定义AOP切点:
 execution(* aop.cglib.aspect.service.WorkService.*(..))  : 匹配WorkService接口的所有方法。
 execution(* aop.cglib.aspect.service.WorkService+.*(..)) : 匹配WorkService接口以及子类型(EmployeeService接口)的所有方法。
3) 通过包定义AOP切点:
在包名字符串中,"aop..impl..*"表示包aop经过任意个包到impl包(这个impl既包括service下的impl包,也包括dao包下的impl包),其中".*"可表示类下的所有函数,而“..*”可表示包、子孙包下的所有类。
execution(* aop..impl..*(..)) : 匹配从aop包经过任意包到impl包下的所有类的所有函数(不包括非接口的函数,因为他们不走代理,除非强制使用代理)
4) 通过方法的参数定义AOP切点:
execution(* devOps(String, long)): 匹配任意类中的devOps()方法,并且第一个参数为String类型,第二个参数为long类型
execution(* devOps(String,..))   : 匹配任意类中的devOps()方法,并且第一个参数为String类型,后面可以有任意个并且类型不限的参数
另外还有within、this、target、args等多个表达式,再次不做表述。
2、Spring+AspectJ框架测试实战案例
环境:Idea+jdk1.8+aspectjweaver(1.9.4)
2.1 案例源代码记录:
首先定义bean文件,将切面的扫描范围定义为"aop.cglib.aspect"
package aop.cglib.aspect.aop;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = "aop.cglib.aspect")
@EnableAspectJAutoProxy(proxyTargetClass=true,exposeProxy=true)//采用cglib动态代理,并暴露代理
public class BeansConfigCglib {
}
定义切面文件AspectConfigCglib:
package aop.cglib.aspect.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectConfigCglib {
    @Pointcut("execution(* aop..service.WorkService.*(..))")
    private void pointCut() {
    }
    /**
     * 环绕通知
     */
    @Around("pointCut()")
    public Object testAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object obj = new Object();
        String className = null;
        String methodName = null;
        try {
            className = joinPoint.getTarget().getClass().getSimpleName();
            methodName = joinPoint.getSignature().getName();
            System.out.println(className + "." + methodName + " start !"); //进入的记录类名+函数名
            obj = joinPoint.proceed();
        } catch (Exception e) {
            System.out.println(" error Message : " + e.getMessage()); //记录报错信息
            e.printStackTrace();
        }
        System.out.println(className + "." + methodName + " end !"); //记录结束的类名+函数名
        return obj;
    }
}
定义接口文件WorkService:
package aop.cglib.aspect.service;
public interface WorkService {
    void developProgram();  //开发工作
    void devOps(); //运维工作
}
定义接口文件WorkService的子接口文件EmployeeService:
package aop.cglib.aspect.service;
public interface EmployeeService extends WorkService {
    void workExperience();  //工作经历
}
定义WorkService接口的实现类WorkServiceImpl:
package aop.cglib.aspect.service.impl;
import aop.cglib.aspect.service.WorkService;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
@Service("workService")
public class WorkServiceImpl implements WorkService {
    @Override
    public void developProgram() {
        System.out.println("开发程序正常进行中...");
        System.out.println("隐含的对象this : " + this);
        devOps(); //直接调用另一个实现接口的函数
        ((WorkServiceImpl) AopContext.currentProxy()).qualityControl(); //强制使用代理
    }

    @Override
    public void devOps() {
        System.out.println("运维工作正常进行中...");
    }

    public void qualityControl() { //定义没有接口的函数
        System.out.println("质量管控工作正常进行中...");
    }
}
定义EmployeeService接口的实现类EmployeeServiceImpl:
package aop.cglib.aspect.service.impl;
import aop.cglib.aspect.service.EmployeeService;
import org.springframework.stereotype.Service;
@Service("EmployeeService")
public class EmployeeServiceImpl implements EmployeeService {
    @Override
    public void workExperience() {
        System.out.println("雇员的工作经历...");
    }

    @Override
    public void developProgram() {
        System.out.println("雇员在程序开发中...");
    }

    @Override
    public void devOps() {
        System.out.println("雇员在程序运维中...");
    }
}
定义测试类WorkServiceTest:
package aop;
import aop.cglib.aspect.service.EmployeeService;
import aop.cglib.aspect.service.WorkService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class WorkServiceTest {
    @Autowired
    private WorkService workService;

    @Autowired
    private EmployeeService employeeService;

    @Test
    public void testStaticProxy(){
        workService.developProgram();
        employeeService.developProgram();
        employeeService.workExperience();
    }
}
注意:测试类的路径需要与启动类的路径一致, 否则会报错。此处启动类的路径为aop.SpringBoot2Application,因此将测试启动类的路径设定为aop.WorkServiceTest
执行结果如下:
WorkServiceImpl.developProgram start !
开发程序正常进行中...
隐含的对象this : aop.cglib.aspect.service.impl.WorkServiceImpl@1a2909ae
运维工作正常进行中...
质量管控工作正常进行中...
WorkServiceImpl.developProgram end !
EmployeeServiceImpl.developProgram start !
雇员在程序开发中...
EmployeeServiceImpl.developProgram end !
雇员的工作经历...
注意:1) devOps函数执行前后没有显示类名+方法名+start和类名+方法名+end。说明devops并未走代理,而是走的自调用,即隐式的this.devops()。没有实现接口的函数通常是不会走代理的,这点通过隐含的对象this打印也可从侧面得出,如果要强制要求走代理,需要满足两个条件:
(1)暴露代理,修改exposeProxy属性。(即exposeProxy=true).将proxyTargetClass属性设置为true,使之使用cglib动态代理
(2)采用以下方式强制走代理,如:((WorkServiceImpl) AopContext.currentProxy()).qualityControl();
2) WorkServiceImpl文件中的qualityControl()函数并未使用代理。因为execution表达式中只定义WorkService接口下的函数
3) EmployeeServiceImpl文件中的workExperience()函数也未使用代理。
接下来我们将execution表达式改为 @Pointcut("execution(* aop..service.WorkService+.*(..))"),执行结果如下
WorkServiceImpl.developProgram start !
开发程序正常进行中...
隐含的对象this : aop.cglib.aspect.service.impl.WorkServiceImpl@78010562
运维工作正常进行中...
WorkServiceImpl.qualityControl start !
质量管控工作正常进行中...
WorkServiceImpl.qualityControl end !
WorkServiceImpl.developProgram end !
EmployeeServiceImpl.developProgram start !
雇员在程序开发中...
EmployeeServiceImpl.developProgram end !
EmployeeServiceImpl.workExperience start !
雇员的工作经历...
EmployeeServiceImpl.workExperience end !
原文地址:https://www.cnblogs.com/ITBlock/p/15162647.html