跟开振学习Spring AOP第一篇:开启约定编程之路

Posted ykzhen2015

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跟开振学习Spring AOP第一篇:开启约定编程之路相关的知识,希望对你有一定的参考价值。

对于初学者也许Spring IoC相对好理解,你也许会觉得AOP就不好理解了,不要紧,这里博主带领一下大家理解AOP框架。在传统的Spring书籍中会对你介绍十分生涩难懂的,切面,切点,通知,织入等内容,好吧,我必须承认,这些对初学的码农确实很难理解,我们先把这些放下,这篇主要和大家玩约定编程。

首先我们先来一个简单到不能简单的服务接口:

package com.learn.spring.aop.service;

public interface HelloService 
	public void sayHello();


这个接口够简单了吧,不用我解释的,然后给出实现类:

package com.learn.spring.aop.service.impl;

import com.learn.spring.aop.service.HelloService;

public class HelloServiceImpl implements HelloService 

	@Override
	public void sayHello() 
		System.out.println("hello world!!");
	
	


典型的hello world,初学者的问题也没有问题,好吧,假设上面你给你写的,下面是博主设计的,首先是一个拦截器接口:

package com.learn.spring.aop.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public interface Interceptor 

    // 前置通知
    void before();

    // 是否采用环绕通知
    boolean useAround();

    // 环绕通知
    Object around(Object target, Method method, Object[] args)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

    // 事后方法
    void after();

    // 异常返回方法
    void afterThrowing();

    // 正常返回方法
    void afterReturning();




依据这个接口,你是否可以给出一个很简单的实现:

package com.learn.spring.aop.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyInterceptor implements Interceptor 

	@Override
	public void before() 
		System.out.println("事前通知");
	

	@Override
	public boolean useAround() 
		return false;
	

	@Override
	public Object around(Object target, Method method, Object[] args)
			throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 
		System.out.println("环绕通知");
		//环绕通知
		return method.invoke(target, args);
	

	@Override
	public void after() 
		System.out.println("事后通知");

	

	@Override
	public void afterThrowing() 
		System.out.println("异常返回通知");

	

	@Override
	public void afterReturning() 
		System.out.println("正常返回通知");

	


好吧,对于你而言,也不难吧。


好了,来点好玩的,然后我给你一个ProxyBean(com.learn.spring.aop.proxy.ProxyBean)的静态方法:

/**
     * 绑定代理对象
     * @param target 服务对象
     * @param interceptor 拦截器
     * @return 代理对象,该对象你可以强制转换为服务对象的接口类型
     */
    public static Object getProxyBean(Object target, Interceptor interceptor)

然后我们暂且把这个方法返回的对象,记为proxy。

通过注释我们可以知道,我们如果使用ProxyBean.proxy(new HelloServiceImpl (), interceptor),就会返回一个对象proxy,按照约定:它可以通过强制转换为HelloService。例如:

HelloService  helloService = new HelloServiceImpl();
HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
然后你就可以通过proxy去调用方法了,好了,来点好玩的。


如果你用proxy去调用方法,博主和你约定如下(这是一次很重要的约定哦):

1、首先调用拦截器的before方法;

2、如果拦截器的useAround方法,返回为true,则执行拦截器的around方法,不执行target对象原有的sayHello方法;

3、无论结果如何都会执行拦截器的after方法;

4、如果around方法有异常或者原有的target对象的sayHello方法有异常,则执行拦截器的afterThrowing方法,否则执行afterReturning方法。

好了,为了更好的说明,我们来个流程图。

好了,依据我们的约定,你的代码将会被织入这个流程中,假设你再开发中使用了这样的代码进行测试。

package com.learn.spring.aop.main;

import com.learn.spring.aop.proxy.MyInterceptor;
import com.learn.spring.aop.proxy.ProxyBean;
import com.learn.spring.aop.service.HelloService;
import com.learn.spring.aop.service.impl.HelloServiceImpl;

public class ProxyMain 

	public static void main(String[] args) 
		HelloService  helloService = new HelloServiceImpl();
		HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
		proxy.sayHello();
	



那么其结果按照我们约定的流程图,就是:

事前通知
hello world!!
事后通知
正常返回通知

然后我们把MyInterceptor中的useAround方法,修改为返回true,于是就可以看到:

事前通知
环绕通知
hello world!!
事后通知
正常返回通知


跟着我们修改拦截器的around方法,让它抛出异常,如下所示

@Override
public Object around(Object target, Method method, Object[] args)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 
    System.out.println("环绕通知");
    if (args == null || args.length == 0) 
        throw new RuntimeException("测试异常");
    
    //环绕通知
    return method.invoke(target, args);

于是得到这样的打印:

事前通知
环绕通知
事后通知
异常返回通知


这一切都是按照我们流程约定来的,你应该照着我们约定的流程图看这些结果。换句话说,在你知道前面的东西后,你就可以按照约定编程了,你是不需要知道博主是怎么做的,对吧。因为我们有约定,按约定就可以了,何必知道那么底层呢?这也是设计者的思维,设计者希望开发者的东西越简单就越好。吐舌头


那么博主是怎么做到将你开发的服务和拦截器织入到流程图中的呢?一切的奥妙当然是我给你的ProxyBean了,

不过在此之前你需要学习一下动态代理,可以看看博主的文章:

MyBATIS插件原理第一篇——技术基础(反射和JDK动态代理)

ps:在Spring中可以使用JDK动态代理,也可以玩CGLIB动态代理,在默认的情况下Spring会以这样的规则来服务:存在接口,使用JDK动态代理,因为JDK动态代理,必须需要接口,而不存在接口则使用CGLIB动态代理。

好了,我们现在公布ProxyBean的代码:(要懂得动态代理才能看懂,菜鸟补习去吧


package com.learn.spring.aop.proxy;

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

public class ProxyBean implements InvocationHandler 

	private Object target = null;
	private Interceptor interceptor = null;

	/**
	 * 绑定代理对象
	 * @param target 被代理对象
	 * @param interceptor 拦截器
	 * @return 代理对象
	 */
	public static Object getProxyBean(Object target, Interceptor interceptor) 
		ProxyBean proxyBean = new ProxyBean();
		// 保存被代理对象
		proxyBean.target = target;
		// 保存拦截器
		proxyBean.interceptor = interceptor;
		// 生成代理对象
		Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
				proxyBean);
		// 返回代理对象
		return proxy;
	

	/**
	 * 处理代理对象方法逻辑
	 * @param proxy 代理对象
	 * @param method 当前方法
	 * @param args  运行参数
	 * @return 方法调用结果
	 * @throws Throwable 异常
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
		//异常标志
		boolean exceptionFlag = false;
		Object retObj = null;
		//前置通知
		this.interceptor.before();
		try 
			//判断是否启用环绕通知
			if (this.interceptor.useAround()) 
				//环绕通知
				retObj = this.interceptor.around(target, method, args);
			 else 
				//调用目标对象原有方法
				retObj = method.invoke(target, args);
			
		 catch (Exception ex) 
			//异常标志
			exceptionFlag = true;
		
		//事后通知
		this.interceptor.after();
		if (exceptionFlag) 
			//异常返回通知
			this.interceptor.afterThrowing();
			return null;
		 else 
			//正常返回通知
			this.interceptor.afterReturning();
			return retObj;
		
		
	



首先,在getProxyBean方法中,博主将你的服务对象target和拦截器保存起来,然后生成一个代理对象,并且委托给ProxyBean的invoke方法去执行,于是当你用proxy对象调用方法的时候,就会进入到invoke方法中了,然后博主把你的代码,按约定的那样织入到流程图中,于是你就可以按照我们的约定编程了。这里强烈建议初学的同学对invoke方法,这段代码进行一步步调试。这样你理解AOP就快很多。


好了,今天谈了约定编程,如果是我来设计,我倒不希望你知道ProxyBean的源码的内容,值需要知道我和你约定的内容,就可以了,这就叫做封装掉,最难理解的内容。

你可以看到,按照约定的规则,博主就可以将你的代码织入到约定的流程中,同样的Spring也是可以做到的,这就是AOP的内容,而实际上你调试这个例子,你就基本懂得了AOP,下一篇,我们把这篇的概念换一下,你就知道原来AOP也就是那么回事。






以上是关于跟开振学习Spring AOP第一篇:开启约定编程之路的主要内容,如果未能解决你的问题,请参考以下文章

跟开振学习Spring AOP第三篇:为什么要用AOP

SpringBoot脚手架项目002-005.Spring 约定编程Spring AOP

Spring学习

spring之旅第一篇-初识spring

spring学习 十三 注解AOP

Spring框架AOP面向切面编程