SpringAop学习(一)

简介: SpringAop学习(一)

Aop面向切面编程

AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了将不同的关注点分离出来的效果 。

这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

使用Aop的好处

  • 集中处理某一关注点/横切逻辑
  • 可以很方便的添加/删除关注点
  • 侵入性少,增强代码可读性及可维护性

Aop的使用场景

  • 权限控制
  • 事物控制
  • 日志审查
  • 性能监控
  • 异常处理

Aop相关概念

  • Aspect :切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;
  • Join point :连接点,程序执行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。Spring仅支持方法的连接点。
  • Advice :通知,切面在某个连接点执行的操作(分为: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );
  • Pointcut :切入点,如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。

使用Aop的注意事项

  • 不要把重要的业务逻辑放到aop里面去处理,容易忽略
  • 无法拦截static,final,private方法
  • 无法拦截内部方法调用:如一个server的内部的普通方法调用一个有事物的方法,那么普通方法里面执行有事物的方法时,事物并不起作用。
  • SpringAOP代理方式,默认是jdk动态代理,我们可以改为cglib代理。

切面表达式

通配符

1)* 匹配任意数量的字符

2)+ 匹配指定类及其子类

3)…(两个.)一般用于匹配任意数的子包或参数

designators(指示器)

匹配方法

execution()

//匹配任何公共方法
 @Pointcut("execution(public * com.smxy.service.*.*(..))")
 //匹配com.smxy包及子包下Service类中无参方法
 @Pointcut("execution(* com.smxy..*Service.*())")
 //匹配com.smxy包及子包下Service类中的任何只有一个参数的方法
 @Pointcut("execution(* com.smxy..*Service.*(*))")
 //匹配com.smxy包及子包下任何类的任何方法
 @Pointcut("execution(* com.smxy..*.*(..))")
 //匹配com.smxy包及子包下返回值为String的任何方法
 @Pointcut("execution(String com.smxy..*.*(..))")
 //匹配异常
 execution(public * com.smxy.service.*.*(..) throws java.lang.IllegalAccessException)
匹配注解

@target()

@args()

@within()

@annotation()

//匹配方法标注有AdminOnly的注解方法
@Pointcut("@annotation(com.smxy.security.AdminOnly)")
public void annoDemo(){}
//匹配标注有Beta的类底下的方法,要求的annotation的Repository级别为CALSS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoDemo3(){}
//匹配标注有Beta的类底下的方法,要求的annotation的Repository级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void annoDemo4(){}

//匹配传入的参数类标注有Repository注解的方法

@Pointcut("@args(org.springframework.stereotype.Repository)")

public void annoArgsDemo(){}

匹配包/类型

within()

//匹配ProdectService类里头所有的方法
@Pointcut("within(com.smxy.service.ProdectService)")
public void matchType(){}
//匹配com.smxy包及子包下所有类的方法
@Pointcut("within(com.smxy..*)")
public void matchPackage(){}
匹配对象

this()

bean()

target()

//匹配Aop对象的目标对象为指定类型的方法,即DemoDao的aop代理对象的方法
@Pointcut("this(com.smxy.DemoDao)")
public void thisDemo(){}
//匹配实现IDao接口的目标对象(而不是aop代理后的对象)的方
@Pointcut("target(com.smxy.IDao)")
public void targetDemo(){}
//匹配所有以Servce结尾的bean里头的方法
@Pointcut("bean(*Service)")
public void matchPackage(){}
匹配参数

args()

//匹配任何以find开头而且只有一个Long参数的方法
@Pointcut("execution(* *..find*(Long))")
public void argsDemo1(){}
//匹配任何以有一个Long参数的方法
@Pointcut("args(Long)")
public void argsDemo1(){}
//匹配任何以find开头而且第一个参数为Long的方法
@Pointcut("execution(* *..find*(Long,..))")
public void argsDemo3(){}
5种advice注解
  • @before,前置通知
  • @After(finally),后置通知,方法执行完之后
  • @AfterReturning,返回通知,成功执行之后
  • @AfterThrowing,异常通知,抛出异常之后
  • @Around,环绕通知

案例

创建一个springboot的工程,引入相关依赖,做一个拦截接口的demo

切面类

/**
 * @Description: TOTO
 * @author BushRo
 * @date 2019-08-01
 * @version 1.0
 *
 */
@Aspect
@Component
public class HttpRequestAspect {
    private static final Logger log = LoggerFactory.getLogger(HttpRequestAspect.class);
    public static long startTime;
    public static long endTime;
    /*@PointCut注解表示表示横切点,哪些方法需要被横切*/
    /*切点表达式*/
    @Pointcut("execution(public * com.smxy.aop.controller.*.*(..))")
    /*切点签名*/
    public void print() {
    }
    /*@Before注解表示在具体的方法之前执行*/
    @Before("print()")
    public void before(JoinPoint joinPoint) {
        log.info("前置切面before……");
        startTime = System.currentTimeMillis();
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String requestURI = request.getRequestURI();
        String remoteAddr = request.getRemoteAddr();   //这个方法取客户端ip"不够好"
        String requestMethod = request.getMethod();
        String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        //获取参数
        Object[] args = joinPoint.getArgs();
        System.out.println("有"+args.length+"个参数");
        for(Object a:args){
            System.out.println(a);
        }
        log.info("请求url=" + requestURI + ",客户端ip=" + remoteAddr + ",请求方式=" + requestMethod + ",请求的类名=" + declaringTypeName + ",方法名=" + methodName + ",入参=" + args);
    }
    /*@After注解表示在方法执行之后执行*/
    @After("print()")
    public void after() {
        endTime = System.currentTimeMillis() - startTime;
        log.info("后置切面after……");
    }
    /*@AfterReturning注解用于获取方法的返回值*/
    @AfterReturning(pointcut = "print()", returning = "object")
    public void getAfterReturn(Object object) {
        log.info("本次接口耗时={}ms", endTime);
        log.info("afterReturning={}", object.toString());
    }
    /*@PointCut注解表示表示横切点,哪些方法需要被横切*/
    /*切点表达式*/
    @Pointcut("execution(public * com.smxy.aop.controller.*.*(..))")
    public void afterPoincut() {
    }
    @Around("afterPoincut()")
    public String afterDemo(ProceedingJoinPoint joinPoint) {
        System.out.println("环绕通知----before");
        String result=null;
        try {
             result = String.valueOf(joinPoint.proceed(joinPoint.getArgs()));
            System.out.println("环绕通知-----方法的返回值为 "+result);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        } finally {
            System.out.println("环绕通知----after");
        }
        return result;
    }
}

controller被切的方法

@RestController
public class Test {
    @RequestMapping("/index/{id}")
    public String text(@PathVariable(value = "id") String id){
        System.out.println("aop测试");
        return "hello word!";
    }
}

ProceedingJoinPoint 与JoinPoint

ProceedingJoinPoint继承了JoinPoint,只能自环绕通知中使用,二者都可以获取方法参数等信息。

ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行并可以得到返回值,这也是环绕通知和前置、后置通知方法的一个最大区别。

运行访问

http://localhost/index/10

20190801005058484.png

内部方法调用的坑

spring cache的原理是基于动态生成proxy代理机制来对方法的调用进行切面,如果对象的方法是内部调用(即this引用)而不不是外部引用,则会导致proxy失效,那么切面就失效。

案例:使用spring的@Cacheable注解缓存一个查询数据,然后通过内部方法调用这个有加注解的方法。

@Service
public class UserService {
    @Cacheable(cacheNames = "getUser")
    public String getUser(){
        System.out.println("进入查询");
        return "user:bushro";
    }
    //内部方法调用
    public String aoptest(){
        return getUser();
    }
}

测试

    @Test
    public void aoptest(){
        System.out.println(userService.getUser());
        System.out.println(userService.aoptest());
    }

结果走了两次getUser(),按道理来说第一次查询后应该产生缓存,第二次就可以直接拿出来使用,可是第二次还是再查询了一次。

分析:

问题就出在内部调用那里:return getUser(),其实是return this.getUser();this是没有经过aop代理的,所以就还会查询一次。

解决方法

就是要让内部方法调用代理的类

原理:之所以方法类ApplicationContextHolder能够灵活自如地获取ApplicationContext,就是因为spring能够为我们自动地执行了setApplicationContext。只需要这个类实现了ApplicationContextAware接口并且被spring管理。

@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext ctx;
    public static ApplicationContext getContext() {
        return ctx;
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ctx = applicationContext;
    }
}

内部调用修改

@Service
public class UserService {
    @Cacheable(cacheNames = "getUser")
    public String getUser(){
        System.out.println("进入查询");
        return "user:bushro";
    }
    public String aoptest(){
        UserService proxy = ApplicationContextHolder.getContext().getBean(UserService.class);
        return proxy.getUser();
    }
}

再次调用aoptest()方法,解决

20190922230324620.png

相关文章
|
6月前
|
Java Spring 容器
SpringAop
SpringAop
55 0
|
XML 监控 Java
SpringAOP介绍与使用
SpringAOP介绍与使用
68 0
|
Java Spring
|
数据安全/隐私保护
SpringAOP(二)
SpringAOP(二)
|
数据安全/隐私保护
SpringAOP(四)
SpringAOP(四)
|
Java 数据库连接 数据库
SpringAOP(三)
SpringAOP(三)
|
XML Java 数据格式
SpringAOP(一)
SpringAOP(一)
|
Java
SpringAop学习(二)
SpringAop学习(二)
110 1
SpringAop学习(二)
|
XML 缓存 Java
|
XML Java 数据安全/隐私保护
springAOP中的基本概念
springAOP中的基本概念
109 0
springAOP中的基本概念