• 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 !
    
  • 相关阅读:
    Java注解学习
    微信小程序开发的一些基础知识点
    feign请求传送实体类参数的一些摸索
    springcloud bus中踩过的坑
    API网关初接触
    ELKF学习(Elasticsearch+logstash+kibana+filebeat)
    getWriter() has already been called for this response异常的一些问题
    kafka的学习
    如何优化一个丑陋的switch语句!
    项目启动之后进行一些初始化的方法
  • 原文地址:https://www.cnblogs.com/ITBlock/p/15162647.html
Copyright © 2020-2023  润新知