介绍
在开发中我们经常使用oop这种纵向结构来开发,但是却会出现一些横切的功能。譬如,日志记录的功能,我们需要在每个方法执行的详细信息通过日志记录,但是我们为每个方法去写日志,明显不合理。再如异常处理功能,我们需要在每个方法执行抛出的异常都专门处理都不合理。这样就需要AOP面向切面开发来处理横切问题。
AOP术语
- 通知(advice):
通知主要是定义切面是什么以及何时使用。
Before:在接合点之前执行通知。
AfterReturning:在接合点执行完成之后执行通知。
AfterThrowing:如果从接合点抛出了任何异常,都执行通知。
After:接合点执行完成之后,无论是否抛出了异常,都执行通知。
Around:在接合点周围执行通知,意思就是可能在接合点之前执行,也可能在接合点之后执行。 - 连接点(join point):
意思就是代码中的点,在这个点上开始玩切面。效果肯定是向应用程序中插入额外的逻辑。 - 切点(point cut):
用来选择需要执行一个或者多个连接点的表达式。 - 切面(aspect):
切面就是切点和通知的结合。 - 织入(weaving):
将方面与目标对象结合在一起的过程。 - 引入(introduce):
动态地为已经存在的类添加属性和方法。
XML方式实现AOP
配置文件:
<context:component-scan base-package="cn.spy"></context:component-scan>
<context:annotation-config></context:annotation-config>
<aop:config>
<aop:pointcut expression="execution(* cn.spy.service.impl.MyServiceImpl.*(..))" id="pc"/>
<aop:aspect ref="myAop">
<aop:before method="beforeExecute" pointcut-ref="pc"/>
<aop:after method="afterExecute" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
强制设置使用cglib代理:<aop:config proxy-target-class="true">
业务类和接口
public interface MyService {
public void serviceMed();
}
@Component("myService")
public class MyServiceImpl extends BaseLog implements MyService{
@Override
public void serviceMed() {
System.out.println("业务逻辑方法");
}
}
切面类:
@Component("myAop")
public class MyAop extends BaseLog {
public void beforeExecute() {
System.out.println("before execute");
}
public void afterExecute() {
System.out.println("after execute");
}
}
测试类:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
MyService myService = (MyService) context.getBean("myService");
myService.serviceMed();
}
结果:
注解方式实现AOP
切面类:
@Component("myAop")
@Aspect
public class MyAop extends BaseLog {
@Pointcut("execution(* cn.spy.service.impl.MyServiceImpl.*(..))")
public void cutMethod() {
}
@Before("cutMethod()")
public void beforeExecute() {
System.out.println("before execute");
}
@After("cutMethod()")
public void afterExecute() {
System.out.println("after execute");
}
}
强制使用cglib代理:@EnableAspectJAutoProxy(proxyTargetClass=true)
aop自定义注解实现记录操作日志
- 自定义注解
/**
* 日志操作注解
*/
@Documented
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
/**
* 操作所属的模块
* @return
*/
public String module() default "";
/**
* 操作
* @return
*/
public String operation() default "";
}
- 实现切面类
@Slf4j
@Component
@Aspect
public class LogAspect {
@Autowired
private MachineBO machine;
@Autowired
private LogMapper logMapper;
@Pointcut("@annotation(com.sunpy.permissionservice.log.LogOperation)")
public void logPoint() {
}
@AfterReturning(pointcut = "logPoint()", returning = "result")
public void doLog(JoinPoint joinPoint, Object result) {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
LogOperation logOperation = method.getAnnotation(LogOperation.class);
Log log = new Log();
log.setLogModule(logOperation.module());
log.setLogOperation(logOperation.operation());
log.setLogClazz(className);
log.setLogMethod(method.getName());
log.setLogMethodResult(method.getReturnType().getName());
StringBuilder sb = new StringBuilder();
for (Parameter parameter : method.getParameters()) {
sb.append(parameter.getParameterizedType().getTypeName());
sb.append(",");
}
log.setLogMethodParam(sb.toString());
insertLog(log);
}
private void insertLog(Log log) {
long logId = new SnowFlakeIdUtil(machine.getWorkerId(), machine.getDatacenterId()).genNextId();
log.setLogId(logId);
log.setCreateTime(TimeUtil.getLocalDateTime());
logMapper.insert(log);
}
}
- 使用:
切点表达式
- 表达式语法:
execution(<scope> <return-type><fully-qualified-class-name>.*(parameters))
- 例子:
execution(* cn.spy.service.impl.MyServiceImpl.(..))
解释:匹配cn.spy.service.impl.MyServiceImpl类下的所有方法
execution(public void cn.spy.service.impl.MyServiceImpl.(..))
解释:匹配cn.spy.service.impl.MyServiceImpl类下的public void xx();方法。
execution(public void cn.spy.service.impl.MyServiceImpl.*(String,..))
解释:匹配cn.spy.service.impl.MyServiceImpl类下第一个参数为String类型,无返回值的所有公共方法。 - 通配符:
..该通配符匹配方法定义中的任何数量的参数。(也可以匹配任意数量的包)
+该通配符匹配给定类的任何子类。
*该通配符匹配任意数量的字符。