浅谈-Spring AOP
关于 AOP我一直都是懵懂,面试 也经常被问到。有时候 自己做一两个小项目 也 没想到过用AOP,最近把重构项目基本 搞完之后,又想起AOP。从网上查了很多,讲法大致相同。
AOP即面向切面编程,在面向切面编程的思想里面,把功能分为,核心业务功能和周边功能。 所谓的核心业务功能指的是:登陆,增加删除数据等。周边功能指的是:日志统计,性能测试,事务管理等。
周边功能在Spring的面向切面编程AOP思想里,叫切面。在整个Spring里 核心业务功能 和切面分别独立开发,然后把切面功能和核心业务功能"编织"在一起,这就叫AOP。
举个栗子:如果要记录日志,通常是在每个方法 中输出一些日志信息,这就造成高耦合,如果这些功能修改了,输出的日志信息也要修改。
通过动态代理,可以在指定位置执行对应流程。这样就可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置插入这些功能。这样的思想,被称为面向切面编程,亦即AOP。
通知类型 | 标注 |
@Component | 把普通pojo实例化到Spring容器中,相当于<bean id=" " class=" "> |
@Aspect | 切面 |
Before(前置通知) | 目标方法调用之前执行 |
After(后置通知) | 目标方法调用之后执行 |
After-returning(返回通知) | 目标方法执行成功后执行 |
After-throwing(异常通知) | 目标方法抛出异常后执行 |
Around(环绕通知) | 相当于合并了前置和后置 |
/** * 方法切入点(execution:执行) * execution(修饰词 类名.方法名(参数类型)) * execution(* cn.tedu.note.service.UserService.login(...)) * Component (把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>) */ @Component @Aspect public class PointcutAspect { //匹配com.joeqiang.cloudbook.controller包及其子包下的所有类的所有方法 @After("bean(*Service)") public void deBefore(JoinPoint jp) throws Throwable { System.out.println("******************执行后置通知******************"); // 接收到请求,记录请求内容 Long startTimeMillis = System.currentTimeMillis(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); System.out.println("URL :" + request.getRequestURI()); System.out.println("HTTP_METHOD :" + request.getMethod()); // System.out.println("IP:" + request.getRemoteAddr()); //用的最多 通知的签名 Signature signature = jp.getSignature(); System.out.println("CLASS_METHOD :" + jp.getStaticPart()); System.out.println("ARGS:" + Arrays.toString(jp.getArgs())); //代理的是哪一个方法 System.out.println("代理方法名 :" + signature.getName()); //AOP代理类的名字 System.out.println("代理类名字:" + signature.getDeclaringTypeName()); //AOP代理类的类(class)信息 System.out.println("AOP代理类信息:" + signature.getDeclaringType()); //获取RequestAttributes RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); //如果要获取Session信息的话,可以这样写: HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION); User user = (User) session.getAttribute("user"); Long endTimeMillis = System.currentTimeMillis(); //格式化开始时间 String startTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTimeMillis); //格式化结束时间 String endTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(endTimeMillis); if (user != null) { System.out.println("Session信息 :" + user.getName()); System.out.println("操作人 : " + user.getName() + "操作方法 : " + signature.getName() + " 开始时间 : " + startTime + " 结束时间 : " + endTime); } }
@AfterReturning(value = "execution( * com.joeqiang.cloudbook.controller..*.*(..))", returning = "keys", argNames = "keys") public void doAfterReturningAdvice2(String keys) { System.out.println("******************后置返回通知******************"); System.out.println("后置返回通知的返回值:" + keys); } //捕获 Controller 发生的异常信息 @AfterThrowing(value = "bean(*Controller)", throwing = "exception") public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) { System.out.println("******************异常通知******************"); // /目标方法名: System.out.println("方法名:" + joinPoint.getSignature().getName()); System.out.println("异常信息:" + exception.getMessage()); }
性能测试:
/* *对软件的业务层进行性能测试 * * 环绕通知方法: * 1.必须有返回值Object * 2.必须有参数ProceedingJoinPoint * 3.需要在方法中调用 jp.proceed() * 4.必须抛出异常 * 5.返回业务方法的返回值 */ @Component @Aspect public class TimeAspect { @Around("bean(*Service)") public Object test(ProceedingJoinPoint jp) throws Throwable { System.out.println("****************执行环绕通知******************"); long t1 = System.currentTimeMillis(); Object val = jp.proceed();//目标方法 long t2 = System.currentTimeMillis(); long t = t2 - t1; //JoinPoint 对象可以获取目标业务方法的详细信息:方法签名,调用参数 Signature m = jp.getSignature(); //方法名 String name = m.getName(); //Sinnature:签名:这里是方法签名 System.out.println("执行方法 :"+name + "总共用时:" + t+"毫秒"); return val; } }
输出 信息:
@Before("execution(* com.joeqiang.cloudbook.controller.LoginController.login())") public void after() { throw new RuntimeException("不给登陆"); }
输出信息: