Spring学习笔记----AOP编程

先用代码讲一下什么是传统的AOP(面向切面编程)编程

需求:实现一个简单的计算器,在每一步的运算前添加日志。最传统的方式如下:

Calculator.java

package cn.limbo.spring.aop.calculator;

/**
 * Created by Limbo on 16/7/14.
 */
public interface Calculator {
    int add(int i , int j);
    int sub(int i , int j);
    int mul(int i , int j);
    int div(int i , int j);
}

CalculatorImpl.java

package cn.limbo.spring.aop.calculator;

/**
 * Created by Limbo on 16/7/14.
 */
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        System.out.println("The method add begin with [ "+ i +"," + j+" ]");
        System.out.println("The method add end with [ "+ i +"," + j+"]");
        return i + j;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("The method sub begin with [ "+ i +"," + j+" ]");
        System.out.println("The method sub end with [ "+ i +"," + j+" ]");
        return i - j;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("The method mul begin with [ "+ i +"," + j+" ]");
        System.out.println("The method mul end with [ "+ i +"," + j+" ]");
        return i * j;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("The method div begin with [ "+ i +"," + j+" ]");
        System.out.println("The method div end with [ "+ i +"," + j+" ]");
        return i / j;
    }
}

这样就完成了需求,但是我们发现,倘若是要修改日志的信息,那么就需要在具体方法里面改,这样做很麻烦,而且把原本清爽的方法改的十分混乱,方法应该表现的是核心功能,而不是这些无关紧要的关注点,下面用原生的java的方式实现在执行方法时,动态添加输出日志方法

CalculatorImpl.java

package cn.limbo.spring.aop.calculator;

/**
 * Created by Limbo on 16/7/14.
 */
public class CalculatorImpl implements Calculator {
    @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;
    }
}

CalculatorLoggingProxy.java

package cn.limbo.spring.aop.calculator;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Objects;

/**
 * Created by Limbo on 16/7/14.
 */
public class CalculatorLoggingProxy {

    //要代理的对象
    private Calculator target;

    public CalculatorLoggingProxy(Calculator target) {
        this.target = target;
    }

    public Calculator getLoggingProxy(){

        //代理对象由哪一个类加载器负责加载
        ClassLoader loader = target.getClass().getClassLoader();
        //代理对象的类型,即其中有哪些方法
        Class[] interfaces = new Class[]{Calculator.class};
        // 当调用代理对象其中的方法时,执行改代码
        InvocationHandler handler = new InvocationHandler() {
            @Override
            /**
             * proxy:正在返回的那个对象的代理,一般情况下,在invoke方法中不使用该对象
             * method:正在被调用的方法
             * args:调用方法时,传入的参数
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                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;
            }
        };
        Calculator proxy = (Calculator) Proxy.newProxyInstance(loader,interfaces,handler);

        return proxy;
    }

}

Main.java

package cn.limbo.spring.aop.calculator;

/**
 * Created by Limbo on 16/7/14.
 */
public class Main {
    public static void main(String[] args) {
        Calculator calculator = new CalculatorImpl();
        Calculator proxy = new CalculatorLoggingProxy(calculator).getLoggingProxy();

        int result = proxy.add(1,2);
        System.out.println("--->" + result);

        result = proxy.sub(1,2);
        System.out.println("--->" + result);

        result = proxy.mul(3,2);
        System.out.println("--->" + result);

        result = proxy.div(14,2);
        System.out.println("--->" + result);
    }
}

这样写虽然已经简化了代码,而且可以任意修改日志信息代码,但是写起来还是很麻烦!!!

下面我们使用spring自带的aop包实现但是要加入

com.springsource.org.aopalliance-1.0.0.jar

com.springsource.org.aspectj.weaver-1.8.5.RELEASE.jar

这两个额外的包

下面看代码,要点全部卸载代码里面了

Calculator.java

package cn.limbo.spring.aop.impl;

/**
 * Created by Limbo on 16/7/14.
 */
public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

CalculatorImpl.java

package cn.limbo.spring.aop.impl;

import org.springframework.stereotype.Component;

/**
 * Created by Limbo on 16/7/14.
 */
@Component("calculatorImpl")
public class CalculatorImpl implements Calculator {
    @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;
    }
}

LoggingAspect.java

package cn.limbo.spring.aop.impl;

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

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * Created by Limbo on 16/7/14.
 */
//把这个类声明为切面:需要该类放入IOC容器中,再声明为一个切面
@Order(0)//指定切面优先级,只越小优先级越高
@Aspect
@Component
public class LoggingAspect {

    /**
     * 定义一个方法,用于声明切入点表达式,一般的,该方法中再不需要添加其他代码
     * 主要是为了重用路径,使用@Pointcut来声明切入点表达式
     * 后面的其他通知直接使用方法名来引用当前的切入点表达式
     */
    @Pointcut("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))")
    public void declareJointPointExpression()
    {

    }

    //声明该方法是一个前置通知:在目标方法之前执行
    @Before("declareJointPointExpression()")
//  @Before("execution(* cn.limbo.spring.aop.impl.*.*(..))")  该包下任意返回值,任意类,任意方法,任意参数类型
    public void beforeMethod(JoinPoint joinPoint)
    {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("The Method "+ methodName+" Begins With " + args);
    }
    //在目标方法执行之后执行,无论这个方法是否出错
    //在后置通知中还不能访问目标方法的返回值,只能通过返回通知访问
    @After("execution(* cn.limbo.spring.aop.impl.Calculator.*(int,int))")
    public void afterMethod(JoinPoint joinPoint)
    {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The Method "+ methodName+" Ends ");
    }

    /**
     * 在方法正常结束后执行的代码
     * 返回通知是可以访问到方法的返回值
     */
    @AfterReturning(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",returning = "result")
    public void afterReturning(JoinPoint joinPoint , Object result)
    {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The Method " + methodName + " Ends With " + result);
    }

    /**
     *在目标方法出现异常的时候执行代码
     * 可以访问异常对象,且可以指定出现特定异常时再执行通知
     */
    @AfterThrowing(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint,Exception ex)//Exception 可以改成 NullPointerException等特定异常
    {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The Method " + methodName + " Occurs With " + ex);
    }

    /**
     * 环绕通知需要ProceedingJoinPoint 类型参数 功能最强大
     * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法
     * 且环绕通知必须有返回值,返回值即为目标方法的返回值
     */
    @Around("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))")
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint)
    {
        Object result =null;
        String methodName = proceedingJoinPoint.getSignature().getName();
        try {
            //前置通知
            System.out.println("The Method " + methodName + " Begins With " + Arrays.asList(proceedingJoinPoint.getArgs()));
            result = proceedingJoinPoint.proceed();
            // 返回通知
            System.out.println("The Method " + methodName + " Ends With " + result);
        } catch (Throwable throwable) {
            //异常通知
            throwable.printStackTrace();
        }
        System.out.println("The Method " + methodName + " Ends ");
        return result;
    }
}

applicationContext.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置自动扫描的包-->
    <context:component-scan base-package="cn.limbo.spring.aop.impl"></context:component-scan>

    <!--使Aspect注解起作用,自动为匹配的类生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

用xml来配置aop

application-config.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"
	     xmlns:tx="http://www.springframework.org/schema/tx"
	     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

	<bean id="userManager" class="com.tgb.aop.UserManagerImpl"/>

	<!--<bean id="aspcejHandler" class="com.tgb.aop.AspceJAdvice"/>-->
	<bean id="xmlHandler" class="com.tgb.aop.XMLAdvice" />
	<aop:config>
		<aop:aspect id="aspect" ref="xmlHandler">
			<aop:pointcut id="pointUserMgr" expression="execution(* com.tgb.aop.*.find*(..))"/>

			<aop:before method="doBefore"  pointcut-ref="pointUserMgr"/>
			<aop:after method="doAfter"  pointcut-ref="pointUserMgr"/>
			<aop:around method="doAround"  pointcut-ref="pointUserMgr"/>
			<aop:after-returning method="doReturn"  pointcut-ref="pointUserMgr"/>
			<aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/>

		</aop:aspect>
	</aop:config>
</beans>

一共有5类的通知,其中around最为强大,但是不一定最常用,aspect的底层实现都是通过代理来实现的,只能说这个轮子造的不错

时间: 2024-10-25 17:09:38

Spring学习笔记----AOP编程的相关文章

Spring学习笔记AOP(二)

鲁春利的工作笔记,好记性不如烂笔头 要进行AOP编程,首先我们要在spring的配置文件中引入aop命名空间: 引入后AOP命名空间并启动对@AspectJ注解的支持(spring-context-aop-annotation.xml): <beans xmlns="http://www.springframework.org/schema/beans"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&q

Spring学习笔记AOP(四)

鲁春利的工作笔记,好记性不如烂笔头 基于XML配置方式声明切面 Spring使用org.springframework.aop.Advisor接口表示切面的概念,Advisor表示只有一个通知(org.aopalliance.aop.Advice)和一个切入点(org.springframework.aop.Pointcut)的切面.Advisor可以使用<aop:config>标签下的<aop:advisor>标签定义. <aop:advisor id="标识&q

Spring学习笔记-AOP前传之动态代理

假设有如下需求: 写一个计算器类,里面包含加减乘除四个方法.在每个方法开始前打印出该方法开始的消息,在每个方法结束前打印出该方法结束的消息和计算的结果. 普通方法,先写一个借口,然后在接口里实现四个方法.在每个方法里加上要打印的语句.实现代码如下. ArithmeticCalculator接口 package com.spring.aop.helloworld; public interface ArithmeticCalculator { int add(int i, int j); int 

Spring学习笔记AOP(三)

鲁春利的工作笔记,好记性不如烂笔头 基于XML配置方式声明切面

spring学习笔记(19)mysql读写分离后端AOP控制实例

在这里,我们接上一篇文章,利用JNDI访问应用服务器配置的两个数据源来模拟同时操作不同的数据库如同时操作mysql和oracle等.实际上,上个例子可能用来模拟mysql数据库主从配置读写分离更贴切些.既然如此,在本例中,我们就完成读写分离的模拟在web端的配置实例. 续上次的例子,关于JNDI数据源的配置和spring datasource的配置这里不再重复.下面着重加入AOP实现DAO层动态分库调用.可先看上篇文章<spring学习笔记(18)使用JNDI模拟访问应用服务器多数据源实例 >

Spring学习笔记(一)

Spring学习笔记(一) Spring核心思想: IOC:  Inversion Of Control (控制反转) / DI: Dependency Injection (依赖注入) AOP: Aspect Oriented Programming (面向切面编程) IOC 1. 简单的应用 Model package com.wangj.spring.model; public class User { private String username; private String pas

不错的Spring学习笔记(转)

Spring学习笔记(1)----简单的实例 ---------------------------------   首先需要准备Spring包,可从官方网站上下载.   下载解压后,必须的两个包是spring.jar和commons-logging.jar.此外为了便于测试加入了JUnit包.   在Myeclipse中创建Java项目.   编写一个接口类,为了简单,只加入了一个方法.   Java代码   1.package com.szy.spring.interfacebean;  

《Spring学习笔记》:Spring、Hibernate、struts2的整合(以例子来慢慢讲解,篇幅较长)

<Spring学习笔记>:Spring.Hibernate.struts2的整合(以例子来慢慢讲解,篇幅较长) 最近在看马士兵老师的关于Spring方面的视频,讲解的挺好的,到了Spring.Hibernate.struts2整合这里,由于是以例子的形式来对Spring+Hibernate+struts2这3大框架进行整合,因此,自己还跟着写代码的过程中,发现还是遇到了很多问题,因此,就记录下. 特此说明:本篇博文完全参考于马士兵老师的<Spring视频教程>. 本篇博文均以如下这

Spring学习笔记 2014-7-9

Spring需要applicationContext.xml来管理各个Bean,其基本格式: <?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:t