对于aop的理解是对业务代码的增强,比如,记录日志,事务控制,异常处理等等,aop是采用动态代理实现的一种技术,也可以启到降低代码的耦合的作用。
aop具体的概念不做解释,切点,切面这些都可以查到,这里只做代码的实现
切入点表达式execution
/** * execution表达式-切入点表达式: * 语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常) * 修饰符:一般省略不写, * 示例: public 公共方法 * * 任意方法 * 返回值:不可省略,必须 * 示例:void 没有返回值 * String 返回字符串 * * 任意 * 包:一般标注切面应用范围 * 示例 :com.demo.controller 固定包 * com.demo.controller.. controller包下的所有子包,包括自己 * com.demo.*.service demo下的任意service,如com.demo.co.service * 类:与包结合限定范围 * 示例:TestController 固定应用某一个类 * *.Controller 以Controller结尾的所有的类 * Test.* 以Test开头的所有的类 * * 任意类 * 方法名:与包+类结合使用,不可省略 * 示例:beforeMethod 固定某个方法 * before.* 以before开头的方法 * *.after 以after结尾的方法 * * 任意方法 * ():方法参数 * 示例:() 无参 * (int) 一个int类型参数 * (int,String) 一个int类型和一个string类型参数 * (..) 任意参数 * throws异常:一般省略不写 * 示例:throws Exception 抛出可抛异常 * */
五种通知代码实现
@Aspect //表明这个类是切面类
@Component //交由spring管理
public class AspectCommon {
//记录日志
private static Logger LOGGER = LoggerFactory.getLogger(AspectCommon.class);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");
/**
* 前置通知 在切点方法执行之前执行
* @param joinPoint
*/
@Before(value = "execution(* com.demo.controller..*.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String sdate =sdf.format(new Date());
Signature signature = joinPoint.getSignature();
LOGGER.info("{}-方法执行开始,开始时间:{}",signature.getName(),sdate);
}
/**
* 后置通知,在切点方法执行之后执行,但获取不到方法的返回值
* @param joinPoint
*/
@After(value = "execution(* com.demo.controller..*.*(..))")
public void afterMethod(JoinPoint joinPoint){
String sdate =sdf.format(new Date());
Signature signature = joinPoint.getSignature();
LOGGER.info("{}-方法执行结束,结束时间:{}",signature.getName(),sdate);
}
/**
* 环绕通知,相当于@before和@after的组合,在切点方法执行之前和之后执行,可以获取到返回值
* ProceedingJoinPoint是JoinPoint实现子类
* @param proceedingJoinPoint
*/
@Around(value = "execution(* com.demo.controller..*.*(..))")
public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
Object result=null;
// 当前时间
long startTime = System.currentTimeMillis();
String sdate =sdf.format(startTime);
// 待执行的方法
Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
LOGGER.info("{}-方法执行开始,参数-{},结束时间:{}",method.getName(),proceedingJoinPoint.getArgs(),sdate);
try {
//转到切点方法执行
result = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long endTime = System.currentTimeMillis();
String eDate = sdf.format(endTime);
// 方法耗时日志输出
LOGGER.info("{}-方法执行结束,结束时间:{},耗时:{},返回结果【{}】", method.getName(), eDate,(endTime-startTime),result);
}
/**
* 返回通知:切点方法正常执行结束后执行,可以获取到返回值
* 注意:这里不能使用ProceedingJoinPoint,不然会报 ProceedingJoinPoint is only supported for around advice
* @param joinPoint
*/
@AfterReturning(value = "execution(* com.demo.controller..*.*(..))",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName();
LOGGER.info("{}-方法执行结束,返回结果:【{}】", name, result);
}
/**
* 异常通知:在切点方法执行出异常时执行,可返回异常信息
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "execution(* com.demo.controller..*.*(..))",throwing = "e")
public void afterReturningMethod(JoinPoint joinPoint,Exception e){
String name = joinPoint.getSignature().getName();
LOGGER.info("{}-方法执行结束,异常信息:【{}】", name, e.getMessage());
}
}
代码基于spring,需要在spring的配置文件中开启对aop的支持,否则aop不起效果,如果是springboot项目,是不需要在类上标注对应的注解@EnableAspectJAutoProxy
因为在AOP的默认配置属性中,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy
附@PointCut参数类型和区别:
表达式标签
- execution():用于匹配方法执行的连接点
- args(): 用于匹配当前执行的方法传入的参数为指定类型的执行方法
- this(): 用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
- target(): 用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
- within(): 用于匹配指定类型内的方法执行;
- @args():于匹配当前执行的方法传入的参数持有指定注解的执行;
- @target():用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
- @within():用于匹配所以持有指定注解类型内的方法;
- @annotation:用于匹配当前执行方法持有指定注解的方法;
其中execution 是用的最多的,
within和@within
- within(com.test.spring.aop.pointcutexp.*) pointcutexp包里的任意类.
- within(com.test.spring.aop.pointcutexp…*) pointcutexp包和所有子包里的任意类.
- @within(org.springframework.transaction.annotation.Transactional) 带有@Transactional标注的所有类的任意方法.
this
- this(com.test.spring.aop.pointcutexp.Intf) 实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类.
当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型.
@target
- @target(org.springframework.transaction.annotation.Transactional) 带有@Transactional标注的所有类的任意方法.
@annotation
- @annotation(org.springframework.transaction.annotation.Transactional) 带有@Transactional标注的任意方法.
@within和@target针对类的注解,@annotation是针对方法的注解
args 和 @args
- @args(org.springframework.transaction.annotation.Transactional) 参数带有@Transactional标注的方法.
- args(String) 参数为String类型(运行是决定)的方法.
例子
@Pointcut(value="args(param)", argNames="param")
private void pointcut1(String param){}
@Pointcut(value="@annotation(secure)", argNames="secure")
private void pointcut2(Secure secure){}
@Before(value = "pointcut1(param) && pointcut2(secure)", argNames="param, secure")
public void before6(JoinPoint jp, String param, Secure secure) {
……
}
@Pointcut("execution(* com.savage.aop.MessageSender.*(..)) && args(param)")
public void log(){
}
@Before("log(String param)")
public void beforeLog(){
//todo something....
}
//等同于
@Before("execution(* com.savage.aop.MessageSender.*(..)) && args(param)")
public void beforeLog(){
//todo something....
}