spring之AOP

1. AOP就是面向切面编程

面向切面是面向对象中的一种方式而已。在代码执行过程中,动态嵌入其他代码,叫做面向切面编程。常见的使用场景:

i :日志

ii: 事务

iii:数据库操作

....

 

2. 面向切面编程的几个核心概念

IOC/DI 本质是就是Java反射+XML解析。

AOP本质上Java动态代理

 

i:切点:要添加代码的地方称作切点

ii:切面:切点+通知

iii:通知(增强):向切点插入的代码称为通知Advice

iv:连接点:切点的定义

 

首先介绍什么是动态代理,举一个计算器的例子

1、相关类 没有引用依赖

2、定义接口

public interface Calculator {
    int add(int a,int b);

    void min(int a,int b);
}

3、创建接口的实现类

public class CalcultorImpl implements Calculator{
    @Override
    public int add(int a, int b) {
        System.out.println(a+"+"+b+"="+(a+b));
        return a+b;
    }

    @Override
    public void min(int a, int b) {
        System.out.println(a-b);
    }
}

4、定义计算器的代理类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class proxy {
    public Object getInstance(Object obj){
    return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("add")) {
                //加日志
            } else {
                //不加日志
            }
            long start = System.nanoTime();
            //Object invoke = method.invoke(obj,new Object[]{3,3}); //直接将3,3赋值过去了,main里的值被覆盖
            Object invoke = method.invoke(obj, args);//获得main里面的值
            long end = System.nanoTime();
            System.out.println(end - start);
            return invoke;
            }
        });
    }
}

5、结果

public class main {
    public static void main(String[] args) {
        //获取代理对象
        Calculator instance = (Calculator) new proxy().getInstance(new CalcultorImpl());
        instance.add(9,9);
        instance.min(6,7);
    }
}

6、结果

9+9=18
459534
-1
236532

 

spring中AOP的实现

通过注解定义拦截规则

1、引入的依赖以及相关类

2、定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAction {
}

3、定义创建实体类并创建getset方法

import org.springframework.stereotype.Component;

@Component
public class MyCalcultor {

    @MyAction
    public int add(int a, int b){
        //int a = 1/0; 测试异常
        System.out.println("add在执行啊---");
        return a+b;
    }

    // @MyAction
    public String sayHello(String name) {
        return "hello " + name + " !";
    }

    @MyAction
    public void min(int a, int b) {

    }
}

4、java配置

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
* @EnableAspectJAutoProxy注解表示开启Java自动代理
*/
@Configurable
@EnableAspectJAutoProxy
@ComponentScan("com.tang")
public class JavaConfig {
}

实现

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
* 这个类就是切面,该类中的内容包含两大块:切点+通知
*
* @Componet只是将该类注册到Spring容器中
* @Aspect表示该类是一个切面
*/
@Component
@Aspect
public class LogAspect {

    /**
    * 可以单独定义一个方法,专门用来定义切点,然后在其他方法中引用
    */
    @Pointcut("@annotation(MyAction)")
    public void pointcut() {

    }

    /**
    * @param point 参数是指连接点,从这个参数中,可以获取方法名方法参数等信息
    * @Before注解表示这是一个前置通知,该通知会在目标方法执行之前执行
    */
    @Before("pointcut()")
    public void before(JoinPoint point) {
        String name = point.getSignature().getName();
        System.out.println(name + "方法开始执行了...");
    }

    /**
    * 这是一个后置通知,后置通知在目标方法执行结束后执行
    * @param point
    */
    @After("pointcut()")
    public void after(JoinPoint point) {
    System.out.println(point.getSignature().getName() + "方法执行结束了...");
    }

    /**
    * 这是一个返回通知,可以获取目标方法的返回值
    *
    * 方法中的参数名和注解中的参数名是对应的,参数值就是目标方法的返回值
    *
    * 需要注意返回值的类型,返回值的类型必须是参数的类或者子类
    */
    @AfterReturning(value = "pointcut()",returning = "result")
    public void returning(JoinPoint point, Object result) {
    System.out.println(point.getSignature().getName() + "方法的返回值是:" + result);
    }

    /**
    * 这是一个异常通知,该通知在方法执行抛出异常时执行
    *
    * 如果目标方法没有异常,则通知不会被触发
    */
    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void throwing(JoinPoint point,Exception e) {
        System.out.println(">>>>>"+e.getMessage());
    }

    /**
    * 环绕通知是上面四种通知的集大成者,因为这个通知中包含了上面四种通知的功能
    *
    * @param pjp 这个参数类似于Method对象
    * @return
    */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) {
        //pjp.proceed() 就类似于method.invoke()
        try {
            //写在这个位置的,就是前置通知
            Object proceed = pjp.proceed();
            //写在这个位置的,就是后置通知
            return proceed;
        } catch (Throwable throwable) {
            //写在这个位置的,就是异常通知
            throwable.printStackTrace();
        }
        return null;
    }
}

调用方法

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
        MyCalcultor bean = ctx.getBean(MyCalcultor.class);
        bean.add(7, 8);
        //bean.sayHello("zhangsan");
        //bean.min(9, 3);
    }
}

结果

add方法开始执行了...
add在执行啊---
add方法执行结束了...
add方法的返回值是:15

5、xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"                                     xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="com.tang.MyCalcultor" id="myCa"/>
    <bean class="com.tang.LogAspect2" id="logAspect2"/>

    <aop:config>
        <aop:aspect ref="logAspect2" ><!--依赖-->
            <aop:pointcut id="pc1" expression="execution(* com.tang.*.*(..))"/>
            <aop:before method="before" pointcut-ref="pc1"/>
            <aop:after method="after" pointcut-ref="pc1"/>
            <aop:after-returning method="returning" pointcut-ref="pc1" returning="result"/>
        </aop:aspect>
    </aop:config>
</beans>

实现

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
* 这是一个Advice
*/
public class LogAspect2 {
    public void before(JoinPoint point) {
        String name = point.getSignature().getName();
        System.out.println(name + "方法开始执行了...");
    }

    public void after(JoinPoint point) {
        System.out.println(point.getSignature().getName() + "方法执行结束了...");
    }

    public void returning(JoinPoint point, Object result) {
        System.out.println(point.getSignature().getName() + "方法的返回值是:" + result);
    }

    public void throwing(JoinPoint point, Exception e) {
        System.out.println(">>>>>" + e.getMessage());
    }

    public Object around(ProceedingJoinPoint pjp) {
        //pjp.proceed() 就类似于method.invoke()
        try {
            //写在这个位置的,就是前置通知
            Object proceed = pjp.proceed();
            //写在这个位置的,就是后置通知
        return proceed;
        } catch (Throwable throwable) {
            //写在这个位置的,就是异常通知
            throwable.printStackTrace();
        }
        return null;
    }
}

调用方法进行测试

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main2 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new    ClassPathXmlApplicationContext("application.xml");
        MyCalcultor myCalcultor = (MyCalcultor) ctx.getBean("myCa");//与xml中id保持相同
        myCalcultor.add(1,2);
    }
}

结果

add方法开始执行了...
add在执行啊---
add方法执行结束了...
add方法的返回值是:3

Process finished with exit code 0

注意点

这种方式实际上破坏了最小侵入式原则。

通过如下方法简化切点的定义:

/**
* 可以单独定义一个方法,专门用来定义切点,然后在其他方法中引用
*/
@Pointcut("@annotation(MyAction)")
public void pointcut() {
}

 

 

通过方法路径定义拦截规则,实际上只是切点的定义方式变了,其他的还是一样的。

/**
     * 可以单独定义一个方法,专门用来定义切点,然后在其他方法中引用
     *  第一个* 表示目标方法的返回值,可以是任意类型,*表示任意类型
     *  第二个*表示类中的任意方法
     *  两个.表示方法的参数任意(可有可无,类型任意)
     *
     *  例如,想精确定位到add方法,步骤如下:
     *  execution(int com.itbaizhan.service.MyCalcultor.add(int,int))
     */
    @Pointcut("execution(* com.itbaizhan.service.*.*(..))")
    public void pointcut() {
    }

使用表达式,可以较好的满足最小侵入式原则。

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值