在Spring AOP 中,通常需要借助AspectJ 的切点表达式语言来定义切点。重要的是Spring 中仅支持AspectJ切点指示器的一个子集。
Spring 支持的AspectJ的切点指示器
AspectJ 指示器 | 描述 |
args() | 限制连接点匹配参数为执行类型的执行方法 |
@args() | 限制连接点匹配参数由执行注解标注的执行方法 |
execution() | 匹配连接点的执行方法 |
this() | 限制连接点匹配AOP代理的Bean引用类型为指定类型的Bean |
target() | 限制连接点匹配目标对象为指定类型的类 |
@target() | 限制连接点匹配目标对象被指定的注解标注的类 |
within() | 限制连接点匹配匹配指定的类型 |
@within() | 限制连接点匹配指定注解标注的类型 |
@annotation | 限制匹配带有指定注解的连接点 |
Spring AOP 中常用的是:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
throws-pattern?)
匹配所有
execution("* *.*(..)")
匹配所有以set开头的方法
execution("* *.set*(..))
匹配指定包下所有的方法
execution("* com.david.biz.service.impl.*(..))
匹配指定包以及其子包下的所有方法
execution("* com.david..*(..)")
匹配指定包以及其子包下 参数类型为String 的方法
execution("* com.david..*(java.lang.String))
@Service("bookService")
public class BookServiceImpl implements BookService {
private static final Logger logger = LogManager.getLogger(BookServiceImpl.class);
public static final String ADD_BOOK = "insert into t_book(id,name) values(1,'duck-j2ee')";
public static final String DELETE_BOOK = "delete from t_book where id=1";
private JdbcTemplate jdbcTemplate;
@Autowired
private BookDao bookDao;
public void addBook() throws Exception {
Book book = new Book();
book.setName("ibatis");
book.setPrice(11);
bookDao.insert(book);
throw new UnRollbackException("受检查异常,不会回滚");
}
public void deleteBook(int id) {
try {
bookDao.deleteById(id);
} catch (SQLException e) {
logger.error("", e);
}
}
@LoggingRequired
public void addNewBook(String name, int price) {
try {
Book book = new Book();
book.setName(name);
book.setPrice(price);
bookDao.insert(book);
List<Book> lists = bookDao.selectAll();
System.out.println(lists);
} catch (SQLException e) {
logger.error("", e);
}
}
public void addUserBook() {
jdbcTemplate.execute("insert into t_book(id,name) values(3,'UserBook')");
}
/**
* Setter method for property <tt>jdbcTemplate</tt>.
*
* @param jdbcTemplate value to be assigned to property jdbcTemplate
*/
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
/**
* @see com.david.biz.service.BookService#queryAll()
*/
public List<Book> queryAll() {
try {
return bookDao.selectAll();
} catch (SQLException e) {
logger.error("", e);
}
return null;
}
}
/**
* execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
throws-pattern?)
*arg() 限制连接点匹配参数为指定类型的执行方法
*@args() 限制连接点匹配参数由执行注解标注的执行
*execution() 用于匹配连接点的执行方法
*this() 限制连接点匹配AOP代理的Bean引用为执行类型的类
*target() 限制连接点匹配目标对象为指定类型的类
*@target() 限制连接点匹配特定的执行对象,这些对象应具备指定的注解类型
*@annotation()限制匹配带有指定注解的连接点
*
*
*
* @author zhangwei_david
* @version $Id: LogAspect.java, v 0.1 2014年11月29日 下午1:10:13 zhangwei_david Exp $
*/
@Component
@Aspect
public class LogAspect {
private static final Logger logger = LogManager.getLogger(LogAspect.class);
/**
* 匹配参数是任何类型,任何数量 且在com,david.biz包或子包下的方法
*/
@Pointcut("args(..)&&within(com.david.biz..*)")
public void arg() {
}
@Pointcut("@args(com.david.aop.LoggingRequired)")
public void annotationArgs() {
}
@Pointcut("@annotation(com.david.aop.LoggingRequired)")
public void logRequiredPointcut() {
}
@Pointcut("args(java.lang.String,*)")
public void argsWithString() {
}
@Pointcut("target(com.david.biz.service.impl.BookServiceImpl)")
public void targetPointcut() {
}
@Pointcut("@target(org.springframework.stereotype.Service)")
public void targetAnnotation() {
}
// @Around("execution(* org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(..))")
// public Object aa(ProceedingJoinPoint pjp) throws Throwable {
// try {
// Object retVal = pjp.proceed();
// System.out.println(retVal);
// return retVal;
// } catch (Exception e) {
// System.out.println("异常");
// return null;
// }
// }
@Before(value = "logRequiredPointcut()")
public void before(JoinPoint joinPoint) {
LogUtils.info(logger,
" 连接点表达式@annotation(com.david.aop.LoggingRequired) - method={0} has been visited",
joinPoint.getSignature().getName());
}
@Before(value = "arg()")
public void beforeArg(JoinPoint joinPoint) {
LogUtils.info(logger,
"连接点表达式:args(..)&&within(com.david.biz..*) method ={0}, args ={1},target={2}",
joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
.getClass().getName());
}
@Before(value = "argsWithString()")
public void beforeArgWithString(JoinPoint joinPoint) {
LogUtils.info(logger, "连接点表达式:args(java.lang.String,*) method={0} ,args ={1},target={2}",
joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
.getClass().getName());
}
@Before(value = "annotationArgs()")
public void beforeAnnotationArgs(JoinPoint joinPoint) {
LogUtils
.info(
logger,
"连接点表达式:@args(com.david.annotation.validate.Length,*) method={0} ,args ={1},target={2}",
joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
.getClass().getName());
}
@Before(value = "targetPointcut()")
public void beforeTarget(JoinPoint joinPoint) {
LogUtils
.info(
logger,
"连接点表达式:target(com.david.biz.service.impl.BookServiceImpl) method={0} ,args ={1},target={2}",
joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
.getClass().getName());
}
@Before(value = " targetAnnotation()")
public void beforeTargetAnnotation(JoinPoint joinPoint) {
LogUtils
.info(
logger,
"连接点表达式:@target(org.springframework.stereotype.Service) method={0} ,args ={1},target={2}",
joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
.getClass().getName());
}
}
2014-12-01 11:14:39 [ main:1577 ] - [ INFO ] 连接点表达式@annotation(com.david.aop.LoggingRequired) - method=addNewBook has been visited
2014-12-01 11:14:39 [ main:1587 ] - [ INFO ] 连接点表达式:args(..)&&within(com.david.biz..*) method =addNewBook, args =Object[][{Junit Test,1000}],target=com.david.biz.service.impl.BookServiceImpl
2014-12-01 11:14:39 [ main:1588 ] - [ INFO ] 连接点表达式:args(java.lang.String,*) method=addNewBook ,args =Object[][{Junit Test,1000}],target=com.david.biz.service.impl.BookServiceImpl
2014-12-01 11:14:39 [ main:1588 ] - [ INFO ] 连接点表达式:target(com.david.biz.service.impl.BookServiceImpl) method=addNewBook ,args =Object[][{Junit Test,1000}],target=com.david.biz.service.impl.BookServiceImpl
2014-12-01 11:14:39 [ main:1589 ] - [ INFO ] 连接点表达式:@target(org.springframework.stereotype.Service) method=addNewBook ,args =Object[][{Junit Test,1000}],target=com.david.biz.service.impl.BookServiceImpl
2014-12-01 11:14:39 [ main:1589 ] - [ INFO ] 连接点表达式:args(..)&&within(com.david.biz..*) method =insert, args =Object[][{Book[id=0,name=Junit Test,price=1000]}],target=com.david.biz.dao.impl.BookDaoImpl
2014-12-01 11:14:39 [ main:1590 ] - [ INFO ] 连接点表达式:@args(com.david.annotation.validate.Length,*) method=insert ,args =Object[][{Book[id=0,name=Junit Test,price=1000]}],target=com.david.biz.dao.impl.BookDaoImpl
2014-12-01 11:14:39 [ main:1591 ] - [ INFO ] 连接点表达式:args(java.lang.String,*) method=insert ,args =Object[][{demo.insert,Book[id=0,name=Junit Test,price=1000]}],target=com.ibatis.sqlmap.engine.impl.SqlMapClientImpl
在AOP中如果还需要获取方法的参数值应该怎么处理呢?
/**
* 获取被拦截方法对象
* <p/>
* MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
* 而缓存的注解在实现类的方法上
* 所以应该使用反射获取当前对象的方法对象
*/
public Method getMethod(ProceedingJoinPoint pjp) {
//获取参数的类型
Class[] argTypes = ((MethodSignature) pjp.getSignature()).getMethod().getParameterTypes();
Method method = null;
try {
method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
} catch (NoSuchMethodException | SecurityException e) {
logger.error("操作日志记录异常 :", e);
}
return method;
}
private PromotionOpLog initPromotionOpLog(OpLogAnnotation opLogAnnotation, Method method, Object[] args) {
PromotionOpLog opLog = new PromotionOpLog();
opLog.setOpModule(opLogAnnotation.opModule());
opLog.setOpAction(opLogAnnotation.opAction());
try {
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u =
new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext(); //把方法参数放入SPEL上下文中
for (int i = 0; i < paraNameArr.length; i++) {
context.setVariable(paraNameArr[i], args[i]);
}
//使用SPEL进行key的解析
if (StringUtils.isNotBlank(opLogAnnotation.opPkId())) {
opLog.setOpPkId(parser.parseExpression(opLogAnnotation.opPkId()).getValue(context, String.class));
}
if (StringUtils.isNotBlank(opLogAnnotation.creator())) {
opLog.setCreator(parser.parseExpression(opLogAnnotation.creator()).getValue(context, String.class));
}
Map<String, Object> opInfo = Maps.newHashMap();
StringBuffer path = new StringBuffer();
path.append(method.getDeclaringClass().getName()).append(".").append(method.getName());
opInfo.put("path", path);
for (int i = 0; i < paraNameArr.length; i++) {
if (i < args.length) {
if (args[i] instanceof Serializable) {
opInfo.put(paraNameArr[i], args[i]);
} else {
String parameterValue = null == args[i] ? "" : args[i].toString();
opInfo.put(paraNameArr[i], parameterValue);
}
} else {
opInfo.put(paraNameArr[i], "");
}
}
opLog.setOpInfo(FastJsonUtil.toJSONString(opInfo));
opLog.setCreateTime(new Timestamp(new Date().getTime()) );
} catch (Exception e) {
logger.info("操作日志记录-解析参数异常", e);
}
return opLog;
}
使用示例:
@OpLogAnnotation(opPkId = "#schemeVO.schemeId", opModule = OpLogConstant.OP_MODULE_ACTIVITY, opAction =
OpLogConstant.OP_ACTION_UPDATE, creator = "#schemeVO.creator")
public BaseResult<Long> saveAndPublishAndPauseScheme(ActivitySchemeVO schemeVO)
这样就可以通过AOP方式记录日志了