spring系列 SpringAOP

 

spring framwork,依赖于core container,不改动原始代码的情况下,对功能代码进行增强。

AOP是一个思想,spring技术对其进行了实现,而aspects也是对AOP的一种实现,aspects是做好了被spring收录的

概念

原来的方法写法

public void save(){        

        Long startTime = System.currentTimeMillis();        

        System.out.println(“book dao save ...”);    

        Long endTime = System.currentTimeMillis();        

        Long totalTime = endTime-startTime;        

        System.out.println("方法耗时:" + totalTime + "ms");    

}

通知

把多个方法耦合的部分抽取出来就叫做通知,设置通知的类就叫做通知类:

public void method(){        

        Long startTime = System.currentTimeMillis();        

        //调用原始操作     

        Long endTime = System.currentTimeMillis();        

        Long totalTime = endTime-startTime;        

        System.out.println("方法耗时:" + totalTime + "ms");    

}

@Component
@Aspect
public class MyAdvice {


    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    @Pointcut("execution(int com.itheima.dao.BookDao.select())")
    private void pt2(){}

    //@Before:前置通知,在原始方法运行之前执行
//    @Before("pt()")
    public void before() {
        System.out.println("before advice ...");
    }

    //@After:后置通知,在原始方法运行之后执行,原方法就算抛异常也会执行
//    @After("pt2()")
    public void after() {
        System.out.println("after advice ...");
    }

    //@Around:环绕通知,在原始方法运行的前后执行

    //借助ProceedingJoinPoint pjp可以调用原来的方法,从而达到对原来方法返回值的处理,如果原来方法有返回值而这里没有,虽然也会执行,但是如果我们想通过代码获取返回值的话就会报错。

        //这里如果ProceedingJoinPoint pjp没有,那就会跳过原始方法执行相应通知,可以用来做校验。

        //这里的规范是用Object,这样即使是个null也能接。

        //调用原始方法的时候会提示我们要抛异常throws Throwable
//    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        //获取原方法的参数,实际值

        //Object[] args = pjp.getArgs();    

        //Object ret = pjp.proceed(args);//在拿到原方法参数值后可以加一些校验逻辑,不需要操作的话不用传参数也可以

        //System.out.println(Arrays.toString(args));

        //获取执行的签名对象
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();        //类路径和类名
        String methodName = signature.getName();        //方法名


        System.out.println("around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        System.out.println("around after advice ...");

        System.out.println("类方法:"+ className+"."+methodName+"执行");
        return ret;
    }

//    @Around("pt2()")
    public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        Integer ret = (Integer) pjp.proceed();
        System.out.println("around after advice ...");
        return ret;
    }

       //@AfterReturning:返回后通知,在原始方法执行完毕后运行,且原始方法执行过程中没有抛异常现象才会执行,执行时机在@after之前

        //如果要操作原始方法的返回值。注解值要改成这样:(value = "pt()",returning = "ret"),然后下面方法写一个形参,形参名要和上面的去接returning 值一样 ,具体类型可以自己根据情况选择。当ProceedingJoinPoint或JoinPoint和我们的返回参数一起被定义的为形参的时候,JoinPoint必须在最前面,不然会报illegalArgumentException。
//    @AfterReturning("pt2()")
    public void afterReturning() {
        System.out.println("afterReturning advice ...");
    }

    //@AfterThrowing:抛出异常后通知,在原始方法执行过程中如果出现异常后就会运行

    //要获取异常对象的话,(value = "pt()",throwing = "t"),下面的参数改成Throwable t
    @AfterThrowing("pt2()")
    public void afterThrowing() {
        System.out.println("afterThrowing advice ...");
    }
}

连接点

这种实际的方法就是一个个的连接点:

public void update(){    

        System.out.println("book dao update ...");

}

public void delete(){    

        System.out.println("book dao delete ...");

}

public void select(){    

        System.out.println("book dao select ...");

}

切入点

指定要具体操作连接点的就是切入点。

代码中指要进行增强的方法。

切入点表达式

切入点表达式标准格式:

动作关键字(访问修饰符  返回值  包名.类/接口名.方法名(参数)异常名)

动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点 访问

修饰符:public,private等,可以省略

异常名:方法定义中抛出指定异常,可以省略

下面两种都行:

execution(void com.xxz.dao.BookDao.update())

execution(void com.xxz.dao.impl.BookDaoImpl.update())

表达式使用通配符:

* :指代文件目录的一层,或一个元素,也可以用来匹配前后缀。

需要注意的是方法参数中要是写了*,那就是匹配的任意参数的,没参数不行

execution(public * com.xx.*.UserService.find*(*))

.. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

execution(public User com..UserService.findById(..))

+:用来匹配子类类型

execution(* *..*Service+.*(..))


切入点通常描述接口,而不描述实现类,因为实现类更可能会随需求改变,耦合度更小。

返回值类型对于增删改类使用精准类型匹配,而查询方法的返回值往往有多种数据结构的,对于查询类使用*通配快速描述。

包名书写使用..匹配时,效率过低,尽量不使用,常用*做单个包描述匹配,或精准匹配。

接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名。

方法名书写以动词进行精准匹配,具体名词可以采用*匹配,例如getById书写成getBy*,而selectAll可以直接书写成selectAll。

通常不使用异常作为匹配规则,异常有相应的处理手段。。

切面

就是将通知和切入点联系起来的部分。

案例

导包

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.4</version>
    </dependency>

配置类:

@EnableAspectJAutoProxy,开启注解开发AOP功能

@Configuration
@ComponentScan("com.xxz")
@EnableAspectJAutoProxy
public class SpringConfig {
}

要被代理的方法save和update:

@Repository
public class BookDaoImpl implements BookDao {

    public void save() {
        System.out.println("book dao save ...");
    }

    public void update(){
        System.out.println("book dao update ...");
    }
}

通知类:

//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类
@Aspect
public class MyAdvice {
    //设置切入点,要求配置在方法上方,这个方法要求无参无返回值无逻辑
    @Pointcut("execution(void com.xxz.dao.BookDao.update())")
    private void pt(){}

    //设置在切入点pt()的前面运行当前操作(前置通知)
    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

启动类:

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
//        bookDao.update();
        System.out.println(bookDao);
        System.out.println(bookDao.getClass());//当方法名不对应目标时,比如void com.xxz.dao.BookDao.update()写成void com.xxz.dao.BookDao.updateD(),对象还是自身。而经过代理的由于重写了toString方法会打出一个proxy对象地址。
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呀吼呀吼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值