AOP(面对切面编程)

引例

若要求对方法进行日志记录,而我们采取每个方法单独写日志代码,将会导致
原有业务急剧膨胀,每个方法处理必须兼顾多个关注点;并且导致代码分散,若
日志条件发生改变则必须修改所有模块
为了解决这个问题,使用AOP编程(面向切面编程)

AOP术语:

切面(Aspect):  横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice):  切面必须要完成的工作
目标(Target): 被通知的对象
代理(Proxy): 向目标对象应用通知之后创建的对象
连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、
方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示
的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为
ArithmethicCalculator#add(); 方位为该方法执行前的位置
切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator
的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切
点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条
件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过
org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

实现方法

一.使用动态代理

好处:-每个事物逻辑位于一个位置,代码不分散,便于维护和升级
-业务模块更简洁,只包含核心业务代码

方法执行需要进行日志记录的类:算数类,实现的自定义接口中包含加减乘除四个方法

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int i, int j) {
        return i+j;
    }

    @Override
    public int sub(int i, int j) {
        return i-j;
    }

    @Override
    public int mul(int i, int j) {
        return i*j;
    }

    @Override
    public int div(int i, int j) {
        return i/j;
    }
}

动态代理类:

public class ArithmeticCalculatorLoggingProxy {
    private ArithmeticCalculator target;

    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
        this.target = target;
    }

    public ArithmeticCalculator getLoggingProxy() {
        ArithmeticCalculator proxy=null;
        //代理对象由哪一个类加载器负责加载
        ClassLoader loader=target.getClass().getClassLoader();
        //代理对象的类型,即其中有哪些方法
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};
        //当调用代理对象其中的方法时,该执行的代码
        InvocationHandler h=new InvocationHandler() {
            /**
             * proxy:正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象
             使用该对象的方法会再次调用invoke,导致死循环。
             * method:正在被调用的方法
             * args:调用方法时,传入的参数
            */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName=method.getName();
                //记录日志
                //执行方法
                try {
                    //前置通知
                    System.out.println("The method"+methodName+" begins with "+ Arrays.asList(args));
                    Object result = method.invoke(target, args);
                    //返回通知,此时可以访问返回值
                    System.out.println("The method"+methodName+" ends with "+ result);
                    return result;
                }catch (Exception e){
                    e.printStackTrace();
                    //异常通知,可以访问到方法出现的异常
                }
                System.out.println("The method"+methodName+" ends with ");
                //后置通知,因为方法可能会出现异常,所以访问不到方法的返回值
                //记录日志
                return null;
            }
        };
        proxy= (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
        return proxy;
    }
}

spring配置文件

<!--配置自动扫描的包-->
    <context:component-scan base-package="com.itlc.spring.aop.impl"></context:component-scan>
    <!--使Aspject注解起作用:自动为匹配的类生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
一.spring AOP

步骤:
1.加入jar包,在idea中还需要导入aopalliance-1.0.jar和aspectjweaver.jar
这两个jar包下载,密码:iqc5
2.在配置文件中加入aop的命名空间

 xmlns:aop="http://www.springframework.org/schema/aop"

3.基于注解的方式
(1).使Aspject注解起作用:自动为匹配的类生成代理对象

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

(2).把横切关注点的代码抽象到切面的类中

1).切面首先是一个IOC中的bean,即加入@Component注解
2).切面还需要加入@Aspect注解

(3)在类中加入各种通知

1).申请一个方法
2).在方法前加入各种通知注解
使用方法为书写完整方法,包括路径与参数类型,用*表示上一个路径中的所有文件,也可用*代替
表示任意申请符,方法中参数的申请符,可以用..来代替所有
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行
通知内部绑定对应方法
     例如:@Before("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(int ,int ))")
    @Before("execution(public int com.itlc.spring.aop.impl.*.*(int ,int ))")
    @Before("execution(* int com.itlc.spring.aop.impl.*.*(..))")

(4)可以在通知方法中声明一个类型为JoinPoint的参数,然后就能访问链接细节,如方法名称和参数值
(5)当有多个切面类绑定一个方法的时候,课用@Order(n),n为阿拉伯数字,表示切面执行的优先级,值越小优先级越高
(6) 在通知标签里中存在大量重复信息

    1)此时定义一个方法,用于作为切入点表达式,一般地,该方法再不需要添加其他的代码
    2)使用@Pointcut来声明切入点表达式
    3)后面的其他通知直接使用方法名来引用当前的切入点表达式
    例:@Pointcut("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(.. ))")
    public void declareJountPointExpression(){}
    则通知的注解可改为:@Before("declareJountPointExpression()")

方法执行需要进行日志记录的类:算数类,实现的自定义接口中包含加减乘除四个方法

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int i, int j) {
        return i+j;
    }

    @Override
    public int sub(int i, int j) {
        return i-j;
    }

    @Override
    public int mul(int i, int j) {
        return i*j;
    }

    @Override
    public int div(int i, int j) {
        return i/j;
    }
}

切面类:

//把这个类声明为一个切面:需要把该类放入IOC容器中,再声明为一个切面
@Aspect
@Component
public class LoggingAspect {

    /*
    * 定义一个方法,用于作为切入点表达式,一般地,该方法再不需要添加其他的代码
    * 使用@Pointcut来声明切入点表达式
    * 后面的其他通知直接使用方法名来引用当前的切入点表达式
    * */
    @Pointcut("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(.. ))")
    public void declareJountPointExpression(){}

    /*声明该方法是一个前置通知:在目标方法开始之前执行
    使用方法为书写完整方法,包括路径与参数类型,用*表示上一个路径中的所有文件,也可用*代替
    表示任意申请符*/
    @Before("declareJountPointExpression()")
    public void beforeMethod(JoinPoint joinPoint){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" begins with" + args);
    }
    //声明该方法是一个后置通知:无论是否发生异常,在目标方法开始之后执行
    //注意:不能在后置通知中访问目标方法执行的结果,因为方法可能出现异常
    @After("declareJountPointExpression()")
    public void afterMethod(JoinPoint joinPoint){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" ends");
    }
    /*
    * 返回通知
    * 在方法正常结束受执行的代码
    * 返回通知是可以访问到方法的返回值的
    * */
    @AfterReturning(value="declareJountPointExpression()",returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" ends with" + result);
    }
    /*
    * 在目标方法方法出现异常时会执行的代码
    * 可以访问到异常对象;
    * 且可以指定在出现特定异常时再执行通知代码:将形参列表中的Exception换成特定的异常类名即可
    * */
    @AfterThrowing(value="declareJountPointExpression()",throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" occurs exception" + ex);
    }

    /*
    * 环绕通知需要携带ProceedingJoinPint
    * 环绕通知类似于动态代理的全过程:ProceedingJoinPint 类型的参数可以决定是否执行目标方法
    * 且环绕通知必须有返回值,返回值即为目标方法的返回值
    * 环绕通知功能最强,但并不是最常用的
    * */
    @Around("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(..))")
    public Object aroundMethod(ProceedingJoinPoint pjd){
       Object result=null;
       String methodName=pjd.getSignature().getName();
       try {
           //前置通知
           System.out.println("The method "+methodName+" begins with" + Arrays.asList(pjd.getArgs()));
           result=pjd.proceed();
           //返回通知
           System.out.println("The method "+methodName+" ends with "+result);
       } catch (Throwable throwable) {
           //异常通知
           System.out.println("The method "+methodName+" occur exception:"+throwable);
       }
       //后置通知
        System.out.println("The method "+methodName+" ends");
       return result;
    }

}

spring配置:

<!--配置自动扫描的包-->
    <context:component-scan base-package="com.itlc.spring.aop.impl"></context:component-scan>
    <!--使Aspject注解起作用:自动为匹配的类生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

Main:

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator arithmeticCalculator=ctx.getBean(ArithmeticCalculator.class);
        int result=arithmeticCalculator.add(3,6);
        System.out.println(result);
        result=arithmeticCalculator.div(4,0);
        System.out.println(result);
    }
}

三.通过配置文件来实现AOP
原理类似切面类来实现,不过不用注解标注
方法执行需要进行日志记录的类:算数类,实现的自定义接口中包含加减乘除四个方法

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int i, int j) {
        return i+j;
    }

    @Override
    public int sub(int i, int j) {
        return i-j;
    }

    @Override
    public int mul(int i, int j) {
        return i*j;
    }

    @Override
    public int div(int i, int j) {
        return i/j;
    }
}

切面类1:

public class LoggingAspect {

    public void beforeMethod(JoinPoint joinPoint){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" begins with" + args);
    }
    public void afterMethod(JoinPoint joinPoint){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" ends");
    }
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" ends with" + result);
    }

    public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" occurs exception" + ex);
    }

 public Object aroundMethod(ProceedingJoinPoint pjd){
       Object result=null;
       String methodName=pjd.getSignature().getName();
       try {
           //前置通知
           System.out.println("The method "+methodName+" begins with" + Arrays.asList(pjd.getArgs()));
           result=pjd.proceed();
           //返回通知
           System.out.println("The method "+methodName+" ends with "+result);
       } catch (Throwable throwable) {
           //异常通知
           System.out.println("The method "+methodName+" occur exception:"+throwable);
       }
       //后置通知
        System.out.println("The method "+methodName+" ends");
       return result;
    }

}

切面类2:

public class VlidationAspect {
    public void beforeMethod(JoinPoint joinPoint){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("VlidationAspect-->  The method "+methodName+" begins with" + args);
    }
    public void afterMethod(JoinPoint joinPoint){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("VlidationAspect-->  The method "+methodName+" ends");
    }
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("VlidationAspect-->  The method "+methodName+" ends with" + result);
    }

    public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
        //传入的参数,包括调用的方法名都在joinPoint这个对象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("VlidationAspect-->  The method "+methodName+" occurs exception" + ex);
    }

}

spring配置文件

<!--配置bean-->
    <bean id="arithmeticCalculator"
    class="com.itlc.spring.aop.impl.ArithmeticCalculatorImpl"></bean>

    <!--配置切面的bean-->
    <bean id="LoggingAspect" class="com.itlc.spring.aop.impl.LoggingAspect"></bean>
    <bean id="vlidationAspect" class="com.itlc.spring.aop.aopByBean.VlidationAspect"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切点表达式-->
        <aop:pointcut id="pointcut" expression="execution(* com.itlc.spring.aop.impl.ArithmeticCalculator.*(..))"/>
        <!--配置切面及通知-->
        <aop:aspect ref="LoggingAspect" order="2">
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
            <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
            <aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut" returning="result"/>
            <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
         <!--<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>-->
        </aop:aspect>
        <aop:aspect ref="vlidationAspect" order="1">
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
        </aop:aspect>
    </aop:config>

Main:

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx=new ClassPathXmlApplicationContext("aopByBean.xml");
        ArithmeticCalculator arithmeticCalculator= (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
        int result=arithmeticCalculator.add(3,6);
        System.out.println(result);
        result=arithmeticCalculator.div(4,0);
        System.out.println(result);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值