spring aop用法

介绍


在开发中我们经常使用oop这种纵向结构来开发,但是却会出现一些横切的功能。譬如,日志记录的功能,我们需要在每个方法执行的详细信息通过日志记录,但是我们为每个方法去写日志,明显不合理。再如异常处理功能,我们需要在每个方法执行抛出的异常都专门处理都不合理。这样就需要AOP面向切面开发来处理横切问题。

AOP术语


  1. 通知(advice):
    通知主要是定义切面是什么以及何时使用。
    Before:在接合点之前执行通知。
    AfterReturning:在接合点执行完成之后执行通知。
    AfterThrowing:如果从接合点抛出了任何异常,都执行通知。
    After:接合点执行完成之后,无论是否抛出了异常,都执行通知。
    Around:在接合点周围执行通知,意思就是可能在接合点之前执行,也可能在接合点之后执行。
  2. 连接点(join point):
    意思就是代码中的点,在这个点上开始玩切面。效果肯定是向应用程序中插入额外的逻辑。
  3. 切点(point cut):
    用来选择需要执行一个或者多个连接点的表达式。
  4. 切面(aspect):
    切面就是切点和通知的结合。
  5. 织入(weaving):
    将方面与目标对象结合在一起的过程。
  6. 引入(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();
    }

结果:


1.jpg

注解方式实现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自定义注解实现记录操作日志


  1. 自定义注解
/**
 * 日志操作注解
 */
@Documented
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
    /**
     *  操作所属的模块
     * @return
     */
    public String module() default "";

    /**
     * 操作
     * @return
     */
    public String operation() default "";

}
  1. 实现切面类
@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);
    }


}
  1. 使用:

切点表达式


  1. 表达式语法:
execution(<scope> <return-type><fully-qualified-class-name>.*(parameters))
  1. 例子:
    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类型,无返回值的所有公共方法。
  2. 通配符:
    ..该通配符匹配方法定义中的任何数量的参数。(也可以匹配任意数量的包)
    +该通配符匹配给定类的任何子类。
    *该通配符匹配任意数量的字符。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容