我使用Spring AOP在我的代码上添加了一些性能检测。我有一个单元测试来测试建议是否被触发。该测试在本地工作但在Jenkins CI工作中间歇性地失败,其中"建议优先级循环错误"
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:157)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:321)
at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.springTestContextPrepareTestInstance(AbstractTestNGSpringContextTests.java:133)
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testTrackingUrlDao' defined in file [***/build/classes/java/test/com/example/analytics/aspect/TestTrackingUrlDao.class]: Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: Advice precedence circularity error
...
Caused by: java.lang.IllegalArgumentException: Advice precedence circularity error
at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.sortAdvisors(AspectJAwareAdvisorAutoProxyCreator.java:82)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:90)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:68)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:359)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:322)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:407)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1461)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
... 66 more
我的around
,afterThrowing
和after
切入点具有相同的正则表达式。最初我没有明确地在建议上设置顺序,并且Spring上下文初始化间歇性地失败了实际代码,而不仅仅是单元测试。阅读完link之后,我重新安排了我在"周围的建议。然后是"在推后" "之后""订单并使用Order
注释添加显式顺序。现在代码在生产中运行良好。但是我们的Jenkins CI工作中的单元测试间歇性地失败(有时会通过,有时会失败)。我在某处读到它说不同的jdk版本可能导致问题。我不确定究竟是什么意思以及如何解决这个问题。由于遗留问题,我仍然在Spring 3.1.x版上,顺便说一句。
这是Aspect代码:
@Aspect
@Component
public class AnalyticsAspect {
@Order(1)
@Around("execution(* com.example.analytics.dao.*.find*(..)) || execution(* com.example.analytics.dao.*.get*(..))")
public Object updateReadCounterTimer(ProceedingJoinPoint joinPoint) throws Throwable {
LOGGER.info("updateReadCounterTimer");
long currTime = System.currentTimeMillis();
Object retVal = joinPoint.proceed();
readCounter.stopWatch.start(currTime);
readCounter.stopWatch.stop();
LOGGER.info("updateReadCounterTimer done");
return retVal;
}
@Order(2)
@Around("execution(* com.example.analytics.dao.*.save*(..)) || execution(* com.example.analytics.dao.*.set*(..))")
public Object updateWriteCounterTimer(ProceedingJoinPoint joinPoint) throws Throwable {
LOGGER.info("updateWriteCounterTimer");
long currTime = System.currentTimeMillis();
Object retVal = joinPoint.proceed();
writeCounter.stopWatch.start(currTime);
writeCounter.stopWatch.stop();
LOGGER.info("updateWriteCounterTimer done");
return retVal;
}
/**
* This is called whenever there's an exception thrown from the matching DAO methods
*/
@Order(3)
@AfterThrowing(pointcut = "execution(* com.example.alytics.dao.*.find*(..)) || execution(* com.example.analytics.dao.*.get*(..))",
throwing = "error")
public void updateErrorReadCounter(JoinPoint jointPoint, Throwable error) {
LOGGER.info("updateErrorReadCounter");
readCounter.failureCounter.increment();
LOGGER.info("updateErrorReadCounter done");
}
@Order(4)
@AfterThrowing("execution(* com.example.alytics.dao.*.save*(..)) || execution(* com.example.analytics.dao.*.set*(..))")
public void updateErrorWriteCounter(JoinPoint jointPoint) {
LOGGER.info("updateErrorWriteCounter");
writeCounter.failureCounter.increment();
LOGGER.info("updateErrorWriteCounter done");
}
/**
* NOTE assumption is that all reader method names will have the "find***" or "get***" pattern
*/
@Order(5)
@After("execution(* com.example.alytics.dao.*.find*(..)) || execution(* com.example.analytics.dao.*.get*(..))")
public void updateSuccessReadCounter(JoinPoint jointPoint) {
LOGGER.info("updateSuccessReadCounter");
readCounter.successCounter.increment();
LOGGER.info("updateSuccessReadCounter done");
}
/**
* NOTE assumption is that all writer method names will have the "save***" or "set***" pattern
*/
@Order(6)
@After("execution(* com.example.analytics.dao.*.save*(..)) || execution(* com.example.analytics.dao.*.set*(..))")
public void updateSuccessWriteCounter(JoinPoint jointPoint) {
LOGGER.info("updateSuccessWriteCounter");
writeCounter.successCounter.increment();
LOGGER.info("updateSuccessWriteCounter done");
}
}
以下是单元测试类的一些片段:
@ContextConfiguration(locations = { "classpath:applicationContext-test.xml" })
public class AnalyticsAspectTest extends AbstractTestNGSpringContextTests {
@Autowired
@Qualifier("testTrackingUrlDao")
TrackingUrlDao trackingUrlDao;
@Autowired
@Qualifier("testAnalyticEventDao")
AnalyticEventDao analyticEventDao;
@Autowired
AnalyticsAspect analyticsAspect;
@Test
public void testReadSuccessCounterOnTrackingUrlDaoFindById() {
List<TrackingUrl> trackingUrls = trackingUrlDao.findById("abc");
assertEquals(analyticsAspect.readCounter.successCounter.getValue(), 1);
assertEquals(trackingUrls.size(), 1);
}
...
}
/**
* This is a dummy implementation of AnalyticEventDao interface so that we can autowire it
* into the VideoAnalyticsAspectTest class
*/
@Component
class TestAnalyticEventDao implements AnalyticEventDao {
@Override
public List<AnalyticEvent> findById(String id) {
...
}
@Override
public void save(AnalyticEvent event) {
...
}
}
/**
* This is a dummy implementation of TrackingUrlDao interface so that we can autowire it
* into the AnalyticsAspectTest class
*/
@Component
class TestTrackingUrlDao implements TrackingUrlDao {
// set it to true to simulate exception when DAO method is called
private boolean testFailure = false;
@Override
public List<TrackingUrl> findById(String id) {
...
}
@Override
public void save(TrackingUrl trackingUrl) {
...
}
}
答案 0 :(得分:0)
为什么不通过将每个操作的三个建议(读/写)合并为一个来避免循环问题?此外,您的代码中存在一些问题:
proceed()
之后开始你的秒表,即你什么都没有计时。我修好了。finally
块来修复它。@Aspect
@Component
public class AnalyticsAspect {
private static final Log LOGGER = new Log4JLogger();
private StatisticsCollector readCounter;
private StatisticsCollector writeCounter;
@Around("execution(* com.example.analytics.dao.*.find*(..)) || execution(* com.example.analytics.dao.*.get*(..))")
public Object readTimerAndCounter(ProceedingJoinPoint joinPoint) throws Throwable {
Object retVal = null;
LOGGER.info("Read timer - start");
readCounter.stopWatch.start(System.currentTimeMillis());
try {
retVal = joinPoint.proceed();
}
catch (Exception e) {
readCounter.failureCounter.increment();
LOGGER.info("Read error - counter updated");
throw e;
}
finally {
readCounter.stopWatch.stop();
LOGGER.info("Read timer - stop");
}
readCounter.successCounter.increment();
LOGGER.info("Read success - counter updated");
return retVal;
}
@Around("execution(* com.example.analytics.dao.*.save*(..)) || execution(* com.example.analytics.dao.*.set*(..))")
public Object writeTimerAndCounter(ProceedingJoinPoint joinPoint) throws Throwable {
Object retVal;
LOGGER.info("Write timer - start");
writeCounter.stopWatch.start(System.currentTimeMillis());
try {
retVal = joinPoint.proceed();
}
catch (Exception e) {
writeCounter.failureCounter.increment();
LOGGER.info("Write error - counter updated");
throw e;
}
finally {
writeCounter.stopWatch.stop();
LOGGER.info("Write timer - stop");
}
writeCounter.successCounter.increment();
LOGGER.info("Write success - counter updated");
return retVal;
}
}
如果效果更好,请尝试。当然我无法测试它,因为你没有共享任何应用程序代码。