约定编程——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方法。
完结撒花~我们运行一下测试方法,得到结果如下:
2.为什么使用AOP?
AOP可以减少大量重复的工作,比如,正常执行更新SQL的流程如下:
通过约定流程编程设计之后:
我们只需要完成的只是编写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.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
学习永不止步,一起加油~