先来看一个简单的例子
简易接口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的功能或者将对应的方法织入流程
开发详解
- 确定连接点
任何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("测试多个切面程序");
}
}
- 开发切面并定义切点
有了连接点,还需要一个切面,通过他可以描述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指定目标对象的方法
- (…)表示任意参数进行匹配
- 测试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=张三¬e=2323
结果
around before...............
User [id=1, userName=张三, note=2323]
before.............
User [id=1, userName=张三, note=2323]
around after.............
after...........
afterReturning...............
结论:无论是否发生异常,后置通知(after)都会被执行,发生异常,异常通知(afterThrowing)就会被触发,返回通知(afterReturning)则不会被触发。
- 环绕通知
环绕通知是所有通知中最强的通知,一般的使用场景是需要大幅度修改原有目标对象的服务逻辑时。环绕通知是一个取代原有目标对象方法的通知,当然也提供了回调原有目标对象方法的能力
@Around("pointCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around before...............");
//回调原有目标对象的方法
joinPoint.proceed();
System.out.println("around after.............");
}
- 引入
在测试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;
}
- 通知获取参数
//在前置通知中获取参数
@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的步骤:
- 确定连接点即目标类,就是什么类的什么方法,需要增强的方法
- 自定义切面并定义切点(@Aspect和@PointCut)
- 指定扫描的包
- 在启动类定义切面并启动