在实际开发中对可能会在现场使用中出错的地方加上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 !