• Spring入门(9)-AOP初探


    Spring入门(9)-AOP初探

    0. 目录

    1. 什么是面向切面编程
    2. AOP常见术语
    3. AOP实例
    4. 参考资料

    1. 什么是面向切面编程

    Aspect Oriented Programming(AOP),即面向切面编程。AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。最常见的就是日志记录。举个例子,我们现在提供一个服务查询学生信息服务,但是我们希望记录有谁进行了这个查询。如果按照传统的OOP的实现的话,那我们实现了一个查询学生信息的服务接口(StudentInfoService)和其实现类(StudentInfoServiceImpl.java),同时为了要进行记录的话,那我们在实现类(StudentInfoServiceImpl.java)中要添加其实现记录的过程。这样的话,假如我们要实现的服务有多个呢?那就要在每个实现的类都添加这些记录过程。这样做的话就会有点繁琐,而且每个实现类都与记录服务日志的行为紧耦合,违反了面向对象的规则。那么怎样才能把记录服务的行为与业务处理过程中分离出来呢?看起来好像就是查询学生的服务自己在进行,但却是背后日志记录对这些行为进行记录,并且查询学生的服务不知道存在这些记录过程,这就是我们要讨论AOP的目的所在。AOP的编程,好像就是把我们在某个方面的功能提出来与一批对象进行隔离,这样与一批对象之间降低了耦合性,可以就某个功能进行编程。


    AOP不是Spring特有的,比如AspectJ也是AOP的实现。

    2. AOP常见术语

    1.通知(Advice):

    通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。
    Spring切面可以应用5种类型的通知。

    • Before:在方法调用前调用通知
    • After:在方法调用后调用通知
    • After-returning:在方法成功执行之后调用通知
    • After-throwing:方法抛出异常后调用通知
    • Around:在方法调用之前和调用之后执行自定义行为

    2.连接点(Joinpoint):

    程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。

    3.切入点(Pointcut)

    通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。

    4.切面(Aspect)

    通知和切入点共同组成了切面:时间、地点和要发生的“故事”

    5.引入(Introduction)

    引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)

    6.目标(Target)

    即被通知的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事(AOP让他做爱做的事)

    7.代理(proxy)

    应用通知的对象,详细内容参见设计模式里面的代理模式

    8.织入(Weaving)

    把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
    (1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
    (2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
    (3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术

    3. AOP实例

    1. 先看接口定义

    package com.chzhao.spring.aoptest;
    
    public interface IBaseBusiness {
    
        /**
         * 用作代理的切入点方法
         * 
         * @param obj
         * @return
         */
        public String delete(String obj);
    
        /**
         * 这方法不被切面切
         * 
         * @param obj
         * @return
         */
        public String add(String obj);
    
        /**
         * 这方法切不切呢?可以设置
         * 
         * @param obj
         * @return
         */
        public String modify(String obj);
    
    }
    

    2. 接口实现

    package com.chzhao.spring.aoptest;
    
    public class BaseBusiness implements IBaseBusiness{
    
    	/**
         * 切入点
         */
        public String delete(String obj) {
            System.out.println("==========调用切入点:" + obj + "说:你敢删除我!===========
    ");
            return obj + ":瞄~";
        }
    
        public String add(String obj) {
            System.out.println("================这个方法不能被切。。。============== 
    ");
            return obj + ":瞄~ 嘿嘿!";
        }
    
        public String modify(String obj) {
            System.out.println("=================这个也设置加入切吧====================
    ");
            return obj + ":瞄改瞄啊!";
        }
    
    
    }
    
    

    3. 定义切点

    package com.chzhao.spring.aoptest;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.support.NameMatchMethodPointcut;
    
    /**
     * 定义一个切点,指定对应方法匹配。来供切面来针对方法进行处理<br>
     * 
     * 继承NameMatchMethodPointcut类,来用方法名匹配
     * 
     */
    public class Pointcut extends NameMatchMethodPointcut {
    
    	private static final long serialVersionUID = 3990456017285944475L;
    
    	@SuppressWarnings("rawtypes")
    	@Override
    	public boolean matches(Method method, Class targetClass) {
    		// 设置单个方法匹配
    		this.setMappedName("delete");
    		// 设置多个方法匹配
    		String[] methods = { "delete", "modify" };
    
    		// 也可以用“ * ” 来做匹配符号
    		// this.setMappedName("get*");
    
    		this.setMappedNames(methods);
    		return super.matches(method, targetClass);
    	}
    
    }
    
    

    4. Before

    package com.chzhao.spring.aoptest;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    public class BaseBeforeAdvice implements MethodBeforeAdvice {
    
    	public void before(Method method, Object[] args, Object target)
    			throws Throwable {
    		System.out.println("===========进入beforeAdvice()============ 
    ");
    		System.out.print("准备在" + target + "对象上用");
    		System.out.print(method + "方法进行对 '");
    		System.out.print(args[0] + "'进行删除!
    
    ");
    		System.out.println("要进入切入点方法了 
    ");
    	}
    
    }
    

    5. AfterReturn

    package com.chzhao.spring.aoptest;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.AfterReturningAdvice;
    
    public class BaseAfterReturnAdvice implements AfterReturningAdvice {
    
    	/**
    	 * returnValue :切入点执行完方法的返回值,但不能修改 <br>
    	 * method :切入点方法 <br>
    	 * args :切入点方法的参数数组 <br>
    	 * target :目标对象
    	 */
    
    	public void afterReturning(Object returnValue, Method method,
    			Object[] args, Object target) throws Throwable {
    		System.out.println("==========进入afterReturning()=========== 
    ");
    		System.out.println("切入点方法执行完了 
    ");
    		System.out.print(args[0] + "在");
    		System.out.print(target + "对象上被");
    		System.out.print(method + "方法删除了");
    		System.out.print("只留下:" + returnValue + "
    
    ");
    	}
    
    }
    

    6. AfterThrow

    package com.chzhao.spring.aoptest;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.ThrowsAdvice;
    
    public class BaseAfterThrowsAdvice implements ThrowsAdvice {
    
    	/**
    	 * 通知方法,需要按照这种格式书写
    	 * 
    	 * @param method
    	 *            可选:切入的方法
    	 * @param args
    	 *            可选:切入的方法的参数
    	 * @param target
    	 *            可选:目标对象
    	 * @param throwable
    	 *            必填 : 异常子类,出现这个异常类的子类,则会进入这个通知。
    	 */
    	public void afterThrowing(Method method, Object[] args, Object target,
    			Throwable throwable) {
    		System.out.println("删除出错啦");
    	}
    
    }
    

    7. Around

    package com.chzhao.spring.aoptest;
    
    import java.lang.reflect.Method;
    
    import org.aopalliance.intercept.MethodInvocation;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    public class BaseAroundAdvice implements MethodInterceptor {
    
    	public Object invoke(MethodInvocation invocation) throws Throwable {
    		System.out.println("===========进入around环绕方法!=========== 
    ");
    		// 调用目标方法之前执行的动作
    		System.out.println("调用方法之前: 执行!
    ");
    		// 调用方法的参数
    		Object[] args = invocation.getArguments();
    		// 调用的方法
    		Method method = invocation.getMethod();
    		// 获取目标对象
    		Object target = invocation.getThis();
    		// 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
    		Object returnValue = invocation.proceed();
    		System.out.println("===========结束进入around环绕方法!=========== 
    ");
    		System.out.println("输出:" + args[0] + ";" + method + ";" + target + ";"
    				+ returnValue + "
    ");
    		System.out.println("调用方法结束:之后执行!
    ");
    		return returnValue;
    	}
    
    	public Object intercept(Object arg0, Method arg1, Object[] arg2,
    			MethodProxy arg3) throws Throwable {
    
    		return null;
    	}
    
    }
    

    8. 主方法

    package com.chzhao.spring.aoptest;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class AopMain {
    	public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
            IBaseBusiness business = (IBaseBusiness ) context.getBean("businessProxy");
            business.delete("猫");
        }
    }
    
    

    9. 最重要的配置文件

    <?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:p="http://www.springframework.org/schema/p"
    	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-3.0.xsd     
    	http://www.springframework.org/schema/context     
    	http://www.springframework.org/schema/context/spring-context-3.0.xsd 
    	http://www.springframework.org/schema/aop     
    	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
    	default-autowire="byName">
    
    	<!-- ==============================利用spring自己的aop配置================================ -->
    	<!-- 声明一个业务类 -->
    	<bean id="baseBusiness" class="com.chzhao.spring.aoptest.BaseBusiness" />
    
    	<!-- 声明通知类 -->
    	<bean id="baseBefore" class="com.chzhao.spring.aoptest.BaseBeforeAdvice" />
    	<bean id="baseAfterReturn" class="com.chzhao.spring.aoptest.BaseAfterReturnAdvice" />
    	<bean id="baseAfterThrows" class="com.chzhao.spring.aoptest.BaseAfterThrowsAdvice" />
    	<bean id="baseAround" class="com.chzhao.spring.aoptest.BaseAroundAdvice" />
    
    	<!-- 指定切点匹配类 -->
    	<bean id="pointcut" class="com.chzhao.spring.aoptest.Pointcut" />
    
    	<!-- 包装通知,指定切点 -->
    	<bean id="matchBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    		<property name="pointcut">
    			<ref bean="pointcut" />
    		</property>
    		<property name="advice">
    			<ref bean="baseBefore" />
    		</property>
    	</bean>
    
    	<!-- 使用ProxyFactoryBean 产生代理对象 -->
    	<bean id="businessProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    		<!-- 代理对象所实现的接口 ,如果有接口可以这样设置 -->
    		<property name="proxyInterfaces">
    			<value>com.chzhao.spring.aoptest.IBaseBusiness</value>
    		</property>
    
    		<!-- 设置目标对象 -->
    		<property name="target">
    			<ref local="baseBusiness" />
    		</property>
    		<!-- 代理对象所使用的拦截器 -->
    		<property name="interceptorNames">
    			<list>
    				<value>matchBeforeAdvisor</value>
    				<value>baseAfterReturn</value>     
    			</list>
    		</property>
    	</bean>
    </beans>
    

    4. 参考资料

    Spring In Action 第三版
    aop的常见术语以及aop中常见通知的实现的总结
    Spring AOP 学习例子

  • 相关阅读:
    Oracle修改字段类型
    JS解析JSON字符串
    C#Json转DataTable
    解决前台和后台传值出现中文乱码
    Oracle 存储过程简单语法
    EasyUI 冻结列
    EasyUI 数据网格行过滤
    windows计划任务
    C#日志文件
    bat 读取 ini 配置文件
  • 原文地址:https://www.cnblogs.com/wardensky/p/4216762.html
Copyright © 2020-2023  润新知