Springboot学习笔记(三)——SprinpAOP编程

先来看一个简单的例子
简易接口HelloService

package com.springboot.chapter04.service;

public interface HelloService {
	public void sayHello(String name);
}

实现类HelloServiceImpl

package com.springboot.chapter04.service.impl;

import com.springboot.chapter04.service.HelloService;

public class HelloServiceImpl implements HelloService{
	@Override
	public void sayHello(String name) {
		// TODO Auto-generated method stub
		if(name == null||name.trim()=="") {
			throw new RuntimeException("parameter is null!");
		}else {
			System.out.println("hello"+name);
		}
	}
}

定义拦截器接口

package com.springboot.chapter04.intercept;

import java.lang.reflect.InvocationTargetException;

import com.springboot.chapter04.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();	//事后异常方法,事件发生异常执行
	public boolean useAround();		//是否使用around方法取代原有方法
}

around方法中的参数Invocation类实现

package com.springboot.chapter04.invoke;

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

public class Invocation {
	private Object[] params;
	private Method method;
	private Object target;
	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;
	}
	public Invocation(Object target, Method method, Object[] params) {
		super();
		this.params = params;
		this.method = method;
		this.target = target;
	}
	public Object proceed() throws IllegalAccessException, InvocationTargetException {
		return method.invoke(target, params);
	}
}

其中,proceed方法会以反射的形式去调用原有的方法
自定义拦截器

package com.springboot.chapter04.intercept;
import java.lang.reflect.InvocationTargetException;
import com.springboot.chapter04.invoke.Invocation;
public class MyInterceptor implements Interceptor {
	@Override
	public boolean before() {
		// TODO Auto-generated method stub
		System.out.println("before.............");
		return true;
	}
	@Override
	public void after() {
		// TODO Auto-generated method stub
		System.out.println("after...............");
	}
	@Override
	public Object around(Invocation invocation) throws IllegalAccessException, InvocationTargetException  {
		// TODO Auto-generated method stub
		System.out.println("around before.................");
		Object object = invocation.proceed();
		System.out.println("around after ..............");
		return object;
	}
	@Override
	public void afterReturning() {
		// TODO Auto-generated method stub
		System.out.println("afterreturning ............");
	}
	@Override
	public void afterThrowing() {
		// TODO Auto-generated method stub
		System.out.println("afterThrowing..........");
	}
	@Override
	public boolean useAround() {
		// TODO Auto-generated method stub
		return true;
	}
}

代理类ProxyBean的实现

package com.springboot.chapter04.proxy;

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


import com.springboot.chapter04.intercept.Interceptor;
import com.springboot.chapter04.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 object = **Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() ,proxyBean);**
		return object;
	}
	/**
	*处理代理对象方法逻辑
	 * @param object 代理对象
	 * @param method 当前方法
	 * @param args运行参数
	 * @return 方法调用参数
	 * @throws Throwable 异常
	 */
	@Override
	public Object invoke(Object object, Method method, Object[] args){
		// TODO Auto-generated method stub
		boolean exceptionFlag = false;
		Invocation invocation = new Invocation(target, method, args);
		this.interceptor.before();//before.....
		Object retObj = null;
		try{
			if(this.interceptor.useAround()) {
				retObj = this.interceptor.around(invocation);
			}else {
				retObj = method.invoke(target, args);
			}
		}catch(Throwable e) {
			exceptionFlag = true;
		}
		this.interceptor.after();
		if(exceptionFlag) {
			this.interceptor.afterThrowing();
		}else {
			this.interceptor.afterReturning();
			return retObj;
		}
		return null;
	}
}

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

newProxyInstance用来生成一个代理对象,有3个参数:

  • classloader:类加载器
  • interfaces:绑定的接口,也就是把代理对象绑定到哪些接口之下,可以是多个
  • InvocationHandler :绑定代理对象的逻辑实现
    所谓代理对象就是指目标对象经过拦截器处理之后的对象,与目标对象属于同一类

测试

package com.springboot.chapter04.test;

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

public class Test {
	public static void testProxy() {
		HelloService service = new HelloServiceImpl();
		HelloService proxyBean = (HelloService) ProxyBean.getProxyBean(service, new MyInterceptor());
		proxyBean.sayHello("zhangsan");
		System.out.println("\n************************name is null!****************");
		proxyBean.sayHello(null);
	}
}

结果

before.............
around before.................
hellozhangsan
around after ..............
after...............
afterreturning ............

************************name is null!****************
before.............
around before.................
after...............
afterThrowing..........

AOP的概念

在不修改目标类代码的情况下,使目标类的功能得到增强,AOP可以减少大量重复的工作
最典型的实际应用是数据库事务的管控
先来看使用JDBC代码实现数据库操作

package com.springboot.chapter04.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import com.springboot.chapter04.pojo.User;

public class UserDao {
	public int insertUser(Connection conn,User user) throws SQLException {
		PreparedStatement statement = null;
		try {
			statement = conn.prepareStatement("insert into t_user (username,note) values (?,?)");
			statement.setString(1, user.getUserName());
			statement.setString(2, user.getNote());
			return statement.executeUpdate();
		}finally {
			statement.close();
		}
	}
}

package com.springboot.chapter04.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import com.springboot.chapter04.pojo.User;

public class UserService {
	public int insertUser() {
		UserDao userDao = new UserDao();
		User user = new User();
		user.setUserName("中山");
		user.setNote("note_1");
		Connection conn = null;
		int result = 0;
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/chapter03?serverTimezone=UTC", "root", "root");
			conn.setAutoCommit(false);
			result = userDao.insertUser(conn, user);
			conn.commit();
		} catch (ClassNotFoundException | SQLException e) {
			// TODO Auto-generated catch block
			try {
				conn.rollback();
			} catch (SQLException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}finally {
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return result;
	}
}

这里可以注意到,使用了很多try…catch…finally语句,存在大量的重复工作
所以,数据库的打开和关闭以及事务的提交和回滚都可以用AOP实现

AOP的术语

  • 连接点(join point):对应的是具体被拦截的对象,通常指特定的方法,例如上面代码中的 sayHello方法就是一个连接点,AOP将通过动态代理技术把它织入对应的流程中
  • 切点(point cut):切面不仅仅应用于单个方法,也可能是多个类的不同方法,这时,可以通过正则式和指示器的规则去定义,从而适配连接点(@PointCut)
  • 通知(advice):分为前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)、返回通知(afterreturning advice)和异常通知(afterThrowing advice),它会根据约定织入到流程中
  • 目标对象(target):即被代理的对象,例如上面代码中HelloServiceImpl实例就是一个目标对象,他被代理了
  • 引入(introduction):是指引入新的类和其方法,增强现有Bean的功能
  • 织入(weaving):他是一个动态代理技术,为原有服务对象生成代理对象,然后将与切点定义匹配的连接点拦截,并按约定将各类通知织入约定的流程的过程
  • 切面(aspect):可以定义切点、各类通知和引入的内容,SpringAOP将通过他的消息来增强Bean的功能或者将对应的方法织入流程

开发详解

  1. 确定连接点
    任何AOP编程首先要确定在什么地方需要AOP,也就是需要确定连接点(在spring中就是什么类的什么方法)如下代码,有一个UserService类,一个pringUser方法
package com.springboot.chapter04.aspect.service;
import com.springboot.chapter04.pojo.User;
public interface UserService {
	public void printUser(User user);
	public void manyAspects();
}
package com.springboot.chapter04.aspect.service.impl;

import org.springframework.stereotype.Service;

import com.springboot.chapter04.aspect.service.UserService;
import com.springboot.chapter04.pojo.User;
@Service//
public class UserServiceImpl  implements UserService{

	//@Override
	public void printUser(User user) {
		// TODO Auto-generated method stub
		if(user==null) {
			throw new RuntimeException("检查用户参数是否为空......");
		}else {
			System.out.println(user);
		}
	}
	@Override
	public void manyAspects() {
		// TODO Auto-generated method stub
		System.out.println("测试多个切面程序");
	}
}
  1. 开发切面并定义切点
    有了连接点,还需要一个切面,通过他可以描述AOP其他的信息,用以描述流程的织入
    除了需要一个切面外,还要有一个切点
package com.springboot.chapter04.aspect;

import org.aspectj.lang.JoinPoint;
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;
/**
 * 切面
 * @author Administrator
 *
 */

import com.springboot.chapter04.aspect.validator.UserValidator;
import com.springboot.chapter04.aspect.validator.impl.UserValidatorImpl;
import com.springboot.chapter04.pojo.User;
@Aspect
public class MyAspect {
	//用户检测
	@DeclareParents(value = "com.springboot.chapter04.aspect.service.impl.UserServiceImpl",defaultImpl = UserValidatorImpl.class)
	public UserValidator userValidator;
	/**
	 * 切点
	 */
	@Pointcut("execution(* com.springboot.chapter04.aspect.service.impl.UserServiceImpl.printUser(..))")
	public void pointCut() {}
	
	//在前置通知中获取参数
	@Before("pointCut() && args(user)")
	public void before(JoinPoint joinPoint,User user) {
		Object[] args = joinPoint.getArgs();
		for (Object object : args) {
			System.out.println(object);
		}
		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.........");
	}
	
	@Around("pointCut()")
	public void around(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("around before...............");
		joinPoint.proceed();
		System.out.println("around after.............");
	}
}

代码中使用@PointCut来定义切点,他标注在pointCut方法上

@Pointcut("execution(* com.springboot.chapter04.aspect.service.impl.UserServiceImpl.printUser(..))")
  • execution表示在执行的时候,拦截里面的正则匹配的方法
  • '*'表示任意返回类型的方法
  • com.springboot.chapter04.aspect.service.impl.UserServiceImpl指定目标对象的全限定类名
  • printUser指定目标对象的方法
  • (…)表示任意参数进行匹配
  1. 测试AOP
package com.springboot.chapter04.aspect.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.springboot.chapter04.aspect.service.UserService;
import com.springboot.chapter04.aspect.validator.UserValidator;
import com.springboot.chapter04.pojo.User;

@Controller
@RequestMapping("/user")
public class UserController {
	@Autowired
	private UserService userService=null;
	
	@RequestMapping("/print")
	@ResponseBody
	public User printUser(Integer id,String userName,String note) {
		User user = new User();
		user.setId(id);
		user.setUserName(userName);
		user.setNote(note);
		try {
			userService.printUser(user);//user位空,执行afterThrowing方法
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return user;
	}
	
	@RequestMapping("/vp")
	@ResponseBody
	public User validateAndPrint(Integer id,String userName,String note) {
		User user = new User();
		user.setId(id);
		user.setNote(note);
		user.setUserName(userName);
		UserValidator userValidator = (UserValidator) userService;
		if(userValidator.validator(user)) {
			userService.printUser(user);
		}else {
			System.out.println("user为null");
		}
		return user;
	}
	
	@RequestMapping("/manyAspect")
	public String manyAspect() {
		userService.manyAspects();
		return "manyAspects";
	}
}

其中@ResponseBody,Springmvc会将user转换为json响应请求
配置启动文件

package com.springboot.chapter04.main;



import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.springboot.chapter04.aspect.MyAspect;

@SpringBootApplication(scanBasePackages = {"com.springboot.chapter04.aspect"})
public class Chapter04Application {
	//定义切面
	@Bean(name="myAspect")
	public MyAspect initMyspect() {
		return new MyAspect();
	}

	public static void main(String[] args) {
		SpringApplication.run(Chapter04Application.class, args);
	}

}

浏览器输入:http://localhost:8080/user/print?id=1&userName=张三&note=2323
结果

around before...............
User [id=1, userName=张三, note=2323]
before.............
User [id=1, userName=张三, note=2323]
around after.............
after...........
afterReturning...............

结论:无论是否发生异常,后置通知(after)都会被执行,发生异常,异常通知(afterThrowing)就会被触发,返回通知(afterReturning)则不会被触发。

  1. 环绕通知
    环绕通知是所有通知中最强的通知,一般的使用场景是需要大幅度修改原有目标对象的服务逻辑时。环绕通知是一个取代原有目标对象方法的通知,当然也提供了回调原有目标对象方法的能力
@Around("pointCut()")
	public void around(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("around before...............");
		//回调原有目标对象的方法
		joinPoint.proceed();
		System.out.println("around after.............");
	}
  1. 引入
    在测试AOP的时候,打印了用户信息,如果用户信息为空,则抛出异常。事实上,我们还可以检测用户信息是否为空,如果为空则不再打印。现有的UserService没有这个功能。Spring允许增强这个接口的功能,可以为这个接口引入新的接口,例如,要引入一个用户检测的接口UserValidator
package com.springboot.chapter04.aspect.validator;

import com.springboot.chapter04.pojo.User;

public interface UserValidator {
	//判断用户是否为空
	public boolean validator(User user);
}

package com.springboot.chapter04.aspect.validator.impl;

import com.springboot.chapter04.aspect.validator.UserValidator;
import com.springboot.chapter04.pojo.User;

public class UserValidatorImpl implements UserValidator {

	@Override
	public boolean validator(User user) {
		// TODO Auto-generated method stub
		System.out.println("引入新的接口:"+UserValidator.class.getSimpleName());
		return user!=null;
	}
}

在自定义的切面类中:

//用户检测
	@DeclareParents(value = "com.springboot.chapter04.aspect.service.impl.UserServiceImpl",defaultImpl = UserValidatorImpl.class)
	public UserValidator userValidator;

其中注解@DeclareParents的作用是引入新的类来增强服务,有两个必须配置的属性value和defaultImpl

  • value:指向你要增强功能的目标对象,这里是要增强UserServiceImpl对象
  • defaultImpl:引入增强功能的类,这里配置为UserValidatorImpl,用来提供校验用户是否为空的功能
    测试
@RequestMapping("/vp")
	@ResponseBody
	public User validateAndPrint(Integer id,String userName,String note) {
		User user = new User();
		user.setId(id);
		user.setNote(note);
		user.setUserName(userName);
		UserValidator userValidator = (UserValidator) userService;
		if(userValidator.validator(user)) {
			userService.printUser(user);
		}else {
			System.out.println("user为null");
		}
		return user;
	}
  1. 通知获取参数
//在前置通知中获取参数
	@Before("pointCut() && args(user)")
	public void before(JoinPoint joinPoint,User user) {
		Object[] args = joinPoint.getArgs();
		for (Object object : args) {
			System.out.println(object);
		}
		System.out.println("before.............");
	}

正则式pointCut() && args(user)中,pointCut()表示启用原来定义切点的规则,并且将连接点(目标对象方法)名称为user的参数传进来。
注意:joinpoint类型的参数对于非环绕通知而言,springaop会自动地把他传到通知中;对于环绕通知来说,可以使用ProceedingJoinPoint类型的参数。

多个切面

首先创建3个切面

package com.springboot.chapter04.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;

@Aspect
@Order(1)//指定切面的顺序
public class MyAspect1 {
	@Pointcut("execution(* com.springboot.chapter04.aspect.service.impl.UserServiceImpl.manyAspects(..))")
	public void manyAspect() {}
	
	@Before("manyAspect()")
	public void before() {
		System.out.println("MyAspect1 before.............");
	}
	
	@After("manyAspect()")
	public void after() {
		System.out.println("MyAspect1 after...........");
	}
	
	@AfterReturning("manyAspect()")
	public void afterReturning() {
		System.out.println("MyAspect1 afterReturning...............");
	}
}

其他两个也是如此
其中注解@Order(1)用来指定切面的执行顺序,还可以实现Ordered接口,重写getOrder方法,但使用注解比较方便

在目标类接口的实现类中定义连接点

@Override
	public void manyAspects() {
		// TODO Auto-generated method stub
		System.out.println("测试多个切面程序");
	}

控制器

@RequestMapping("/manyAspect")
	public String manyAspect() {
		userService.manyAspects();
		return "manyAspects";
	}

定义切面

@Bean(name="myAspect1")
	public MyAspect1 initMyspect1() {
		return new MyAspect1();
	}
	@Bean(name="myAspect2")
	public MyAspect2 initMyspect2() {
		return new MyAspect2();
	}
	@Bean(name="myAspect3")
	public MyAspect3 initMyspect3() {
		return new MyAspect3();
	}

测试结果

MyAspect1 before.............
MyAspect2 before.............
MyAspect3 before.............
测试多个切面程序
MyAspect3 after...........
MyAspect3 afterReturning...............
MyAspect2 after...........
MyAspect2 afterReturning...............
MyAspect1 after...........
MyAspect1 afterReturning...............

由结果可知,对于前置通知(before)都是从小到大运行的,而对于后置通知和返回通知都是从大到小运行的,这就是一个典型的责任链模式的顺序

总结

编写AOP的步骤:

  1. 确定连接点即目标类,就是什么类的什么方法,需要增强的方法
  2. 自定义切面并定义切点(@Aspect和@PointCut)
  3. 指定扫描的包
  4. 在启动类定义切面并启动
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值