约定编程——Spring AOP

1.通过一个实例了解什么是约定编程?

强烈建议下载源码自己调试,否则很难弄明白里面调用过程!

首先,新建一个HelloService接口及其实现类HelloServiceImpl,非常简单,代码如下:

package com.springboot.chapter4.service;

public interface HelloService {
	public void sayHello(String name);
}
package com.springboot.chapter4.aspect.service.impl;

import org.springframework.stereotype.Service;

import com.springboot.chapter4.service.HelloService;

@Service
public class HelloServiceImpl implements HelloService {

	@Override
	public void sayHello(String name) {
		if (name == null || name.trim() == "") {
			throw new RuntimeException ("parameter is null!!");
		}
		System.out.println("hello " + name);
	}
}

下面先来定义一个拦截器接口Interceptor,代码如下:

package com.springboot.chapter4.intercept;

import java.lang.reflect.InvocationTargetException;
import com.springboot.chapter4.invoke.Invocation;

public interface Interceptor {
	//事前方法
	public boolean before();
	
	//事后方法
	public void after();
	/**
	 * 取代原有事件方法
	 * @param invocation -- 回调参数,可以通过它的proceed方法,回调原有事件
	 * @return 原有事件返回对象
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 */
	public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
	
	//是否返回方法。事件没有发生异常执行
	public void afterReturning();
	
	//事后异常方法,当事件发生异常后执行
	public void afterThrowing();

	//是否使用around方法取代原有方法
	boolean useAround();
	
}

这里需要注意的是around方法里面的Invocation对象,下面是它的源码:

package com.springboot.chapter4.invoke;

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

public class Invocation {

	private Object[] params;
	private Method method;
	private Object target;
	
	public Invocation(Object target, Method method, Object[] params) {
		this.target = target;
		this.method = method;
		this.params = params;
	}
	
	public Object proceed() throws InvocationTargetException, IllegalAccessException {
		return method.invoke(target, params);
	}

	public Object[] getParams() {
		return params;
	}

	public void setParams(Object[] params) {
		this.params = params;
	}

	public Method getMethod() {
		return method;
	}

	public void setMethod(Method method) {
		this.method = method;
	}

	public Object getTarget() {
		return target;
	}

	public void setTarget(Object target) {
		this.target = target;
	}

	
}

忽略setter和getter方法,除了构造器之外,就是proceed方法,它会以反射的方法去调用原有的方法(通过调试可以看到调用的是sayhello方法)。

接下来,根据拦截器,我们设计自己的实现类MyInterceptor,代码如下:

package com.springboot.chapter4.intercept;

import java.lang.reflect.InvocationTargetException;
import com.springboot.chapter4.invoke.Invocation;

public class MyInterceptor implements Interceptor {

	@Override
	public boolean before() {
		System.out.println("before ......");
		return true;
	}

	@Override
	public boolean useAround() {
		return true;
	}

	@Override
	public void after() {
		System.out.println("after ......");
	}

	@Override
	public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
		System.out.println("around before ......");
		Object obj = invocation.proceed();
		System.out.println("around after ......");
		return obj;
	}

	@Override
	public void afterReturning() {
		System.out.println("afterReturning......");

	}

	@Override
	public void afterThrowing() {
		System.out.println("afterThrowing 。。。。。。");
	}

}

下面是约定的内容:

我们需要设计一个类ProxyBean,里面有个静态方法

public static Object getProxyBean(Object target,Interceptor interceptor)

这里的target对应我们的HelloService,interceptor对应MyInterceptor,通过这个方法返回一个proxy.

下面是测试流程:

package com.springboot.chapter4.main;

import com.springboot.chapter4.aspect.service.impl.HelloServiceImpl;
import com.springboot.chapter4.intercept.MyInterceptor;
import com.springboot.chapter4.proxy.ProxyBean;
import com.springboot.chapter4.service.HelloService;

public class AopMain {

	public static void main(String[] args) {
		testProxy();
	}
	
	private static void testProxy() {
		HelloService helloService = new HelloServiceImpl();
		HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
		proxy.sayHello("zhangsan");
		System.out.println("\n###############name is null!!#############\n");
		proxy.sayHello(null);
	}

}

约定的执行流程如下:

(1)使用proxy调用方法时会先执行拦截器的before方法

(2)如果拦截器的userAround方法返回true,则执行拦截器的around方法,而不去执行sayhello方法,但around方法的参数invocation对象(即MyInterceptor)存在一个proceed方法,它可以调用sayhello方法;如果useAround方法返回false,则直接调用sayhello方法。

(3)无论怎么样,在完成之后,都会执行拦截器的after方法

(4)如果发生异常,则执行拦截器的afterThrowing方法,否则执行afterReturning方法。

最后,最关键的是我们实现ProxyBean,也就是将服务类和拦截方法写入流程,代码如下:

package com.springboot.chapter4.proxy;

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

import com.springboot.chapter4.intercept.Interceptor;
import com.springboot.chapter4.invoke.Invocation;

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)  {
		//异常标识
		boolean exceptionFlag = false;
		Invocation invocation = new Invocation(target, method, args);
		Object retObj = null; 
		try {
			if (this.interceptor.before()) {
				retObj = this.interceptor.around(invocation);
			} else {
				retObj = method.invoke(target, args);
			}
		} catch (Exception ex) {
			//产生异常
			exceptionFlag = true;
		}
		this.interceptor.after();
		if (exceptionFlag) {
			this.interceptor.afterThrowing();
		} else {
			this.interceptor.afterReturning();
			return retObj;
		}
		return null;
	}

}

这里,ProxyBean实现的接口InvocationHandler,它是JDK提供的类Proxy静态方法newProxyInstance里面的一个参数:

public static Object newProxyInstance(ClassLoader classLoader , Class<?>[] interfaces ,InvocationHandler invocationHandler) throws IllegalArgumentException

这里的invocationHandler是接口InvocationHandler的一个对象,它定义了一个invoke方法,所以ProxyBean可以实现这个invoke方法。

完结撒花~我们运行一下测试方法,得到结果如下:

第4章 约定编程——Spring AOP

 

 

2.为什么使用AOP?

AOP可以减少大量重复的工作,比如,正常执行更新SQL的流程如下:

第4章 约定编程——Spring AOP

通过约定流程编程设计之后:

第4章 约定编程——Spring AOP

我们只需要完成的只是编写SQL这一步,然后写入流程,其他的比如数据库资源的打开和关闭、事务的回滚和提交都默认由Spring实现,减少了大量的代码。

3.AOP术语及流程

AOP术语:

(1)连接点(join point):对应的是具体被拦截的对象,因为Spring只能支持方法,所以被拦截的对象往往就是具体的方法,例如,HelloServiceImpl里的sayHello方法

(2)切点(point cut):向Spring描述哪些类的哪些方法需要启用AOP编程,一般是多个方法,scope大于连接点。

(3)通知(advice):约定流程下的方法,分为前置通知(比如before),后置通知(比如after),环绕通知,事后返回通知和异常通知等。

(4)目标对象(target) :被代理对象,例如,约定编程中的HelloServiceImpl实例就是一个目标对象,它被代理了

(5)引入(introduction):引入新的类和方法,增强现有Bean的功能

(6)织入(weaving):通过动态代理技术,为原有服务对象生成代理对象,然后将其与切点定义匹配的连接点拦截。

(7)切面(aspect):一个可以定义切点、各类通知和引入的内容。

概念可能比较抽象,下面通过之气我们那个例子来看一下AOP流程约定:

第4章 约定编程——Spring AOP

4.AOP开发

(1)任何AOP编程,首先要确定的都是在什么地方需要AOP,也就是需要确定连接点(什么类的什么方法)。如:

用户服务接口:

package com.springboot.chapter4.aspect.service;

import com.springboot.chapter4.pojo.User;

public interface UserService {
	
	public void printUser();

	public void printUser(User user);
	
	
	public void manyAspects();
}

用户服务接口实现类:

package com.springboot.chapter4.aspect.service.impl;

import org.springframework.stereotype.Service;

import com.springboot.chapter4.aspect.service.UserService;
import com.springboot.chapter4.pojo.User;

@Service
public class UserServiceImpl implements UserService {
	
	private User user = null;

	@Override
	public void printUser(User user) {
		if (user == null) {
			throw new RuntimeException("检查用户参数是否为空......");
		}
		System.out.print("id =" + user.getId());
		System.out.print("\tusername =" + user.getUsername());
		System.out.println("\tnote =" + user.getNote());
	}

	@Override
	public void manyAspects() {
		System.out.println("测试多个切面顺序");
	}

	@Override
	public void printUser() {
		if (user == null) {
			throw new RuntimeException("检查用户参数是否为空......");
		}
		System.out.print("id =" + user.getId());
		System.out.print("\tusername =" + user.getUsername());
		System.out.println("\tnote =" + user.getNote());
	}
	
	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}
}

(2)创建切面类

通过它可以描述AOP的其他信息,用以描述流程的织入,如:

package com.springboot.chapter4.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import com.springboot.chapter4.aspect.validator.UserValidator;
import com.springboot.chapter4.aspect.validator.impl.UserValidatorImpl;
import com.springboot.chapter4.pojo.User;

@Aspect
public class MyAspect {
	
	@Before("execution(* com.springboot.chapter4.aspect.service.impl.UseServiceImpl.printUser(..))")
	public void before() {
		System.out.println("before ......");
	}
	
	@After(v)
	public void after() {
		System.out.println("after ......");
	}
	
	
	@AfterReturning(v)
	public void afterReturning() {
		System.out.println("afterReturning ......");
	}
	
	@AfterThrowing("execution(* com.springboot.chapter4.aspect.service.impl.UseServiceImpl.printUser(..))")
	public void afterThrowing() {
		System.out.println("afterThrowing ......");
	}
}

(3)定义切点(及正则式分析)

在上面切面的定义中,,有@Before,@After,@AfterReturning等注解,里面定义了一个正则式,作用是定义什么时候启用AOP,我们可以定义切点如下:

package com.springboot.chapter4.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import com.springboot.chapter4.aspect.validator.UserValidator;
import com.springboot.chapter4.aspect.validator.impl.UserValidatorImpl;
import com.springboot.chapter4.pojo.User;

@Aspect
public class MyAspect {
	
	@Pointcut("execution(* com.springboot.chapter4.aspect.service.impl.UserServiceImpl.printUser(..))")
	public void pointCut() {
	}
	
	@Before("pointCut()")
	public void before() {
		System.out.println("before ......");
	}
	
	@After("pointCut()")
	public void after() {
		System.out.println("after ......");
	}
	
	@AfterReturning("pointCut()")
	public void afterReturning() {
		System.out.println("afterReturning ......");
	}
	
	@AfterThrowing("pointCut()")
	public void afterThrowing() {
		System.out.println("afterThrowing ......");
	}
}

其中的正则表达式意思是:

execution(* com.springboot.chapter4.aspect.service.impl.UserServiceImpl.printUser(..))

execution表示在执行的时候,拦截里面的正则匹配的方法

* 表示任意返回类型的方法

(..)表示任意参数进行匹配

5.多个切面

上面只讨论了一个切面的运行,Spring还支持多个切面的运行,不同的是创建多个切面类,还是一个切点,切面同时拦截这个切点方法。

需要注意的是,如果不加@Order注解,切面的执行顺序可能是混乱的,可以通过@Order指定切面顺序,如:

@Aspect
@Order(1)
public class MyAspect1{
    ......
}

 

本节代码已上传Github: https://github.com/lizeyang18/SpringBoot-2.x/tree/master/Chapter4

学习永不止步,一起加油~

 

 

相关文章:

  • 2021-06-18
  • 2022-01-12
  • 2021-09-11
  • 2021-06-30
  • 2022-01-19
  • 2022-03-02
  • 2022-12-23
  • 2021-05-16
猜你喜欢
  • 2021-08-18
  • 2021-05-13
  • 2022-12-23
  • 2021-04-11
  • 2022-12-23
  • 2021-07-05
  • 2021-04-24
相关资源
相似解决方案