本来地址:SpringMVC干货系列:从零搭建SpringMVC+mybatis(四):Spring两大核心之AOP学习
博客地址:tengj.top/javascript
上一篇咱们介绍了Spring的核心概念DI,DI有助与应用对象之间的解耦。今天咱们就来介绍下另外一个很是核心的概念,面向切面编程AOP。php
在软件开发中,散布于应用中多处的功能被称为横切关注点(cross-cutting concern)。一般来说,这些横切关注点从概念上是与应用的业务逻辑相分离的。好比:日志、声明式事物、安全和缓存。这些东西都不是咱们平时写代码的核心功能,但许多地方都要用到。java
把这些横切关注点与业务相分离正是面向切面编程(AOP)索要解决的问题。web
简单的说就是把这些许多地方都要用到,但又不是核心业务的功能,单独剥离出来封装,经过配置指定要切入到指定的方法中去。spring
为了理解AOP,咱们必须先了解AOP的相关术语,很简单不难:express
通知(Advice):
在AOP中,切面的工做被称为通知。通知定义了切面“是什么”以及“什么时候”使用。除了描述切面要完成的工做,通知还解决了什么时候执行这个工做的问题。编程
Spring切面能够应用5种类型的通知:spring-mvc
链接点(Join point):
链接点是在应用执行过程当中可以插入切面的一个点。这个点能够是调用方法时、抛出异常时、甚至修改一个字段时。切面代码能够利用这些点插入到应用的正常流程之中,并添加行为。缓存
切点(Pointcut):
若是说通知定义了切面“是什么”和“什么时候”的话,那么切点就定义了“何处”。好比我想把日志引入到某个具体的方法中,这个方法就是所谓的切点。安全
切面(Aspect):
切面是通知和切点的结合。通知和切点共同定义了切面的所有内容———他是什么,在什么时候和何处完成其功能。
引入(Introduction):
引入容许咱们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。
织入(Weaving):
把切面应用到目标对象来建立新的代理对象的过程,织入通常发生在以下几个时机:
建立切入点来定义切面所织入的链接点是AOP框架的基本功能。
Spring提供了4种类型的AOP支持:
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,所以,Spring对AOP的支持局限于方法拦截。
这里我不许备介绍经典Spring AOP,由于引入了简单的声明式AOP和基于直接的AOP后,Spring经典的AOP看起来就显得很是笨重和过于复杂。
对于新手入门来讲,咱们不须要知道这么多,在这里我也只介绍2,3两种方式,简单的说就是一个基于xml配置,一个基于注解。
下面就直接开始举两个例子分别来介绍下这两种AOP方式,咱们就拿简单的日志来讲明。
首先基于注解的方式须要引入这些包,对用的pom.xml以下:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>复制代码
咱们仍是举前面用到的UserController来讲明,下面方法很简单,执行进入这个方法的时候会打印“进来了”信息,如今我打算给这个方法加日志,在执行该方法前打印“进来前”,在执行完方法后执行“进来后”。
package com.tengj.demo.controller;
@Controller
@RequestMapping(value="/test")
public class UserController {
@Autowired
UserService userService;
@RequestMapping(value="/view",method = RequestMethod.GET)
public String index(){
userService.sayHello("tengj");
return "index";
}
}复制代码
servie层代码:
package com.tengj.demo.service
public interface UserService {
public void sayHello(String name);
}复制代码
servie实现类代码:
package com.tengj.demo.service.impl;
@Service("userService")
public class UserServiceImpl implements UserService{
@Override
public void sayHello(String name) {
System.out.println("hello,"+name);
}
}复制代码
上面方法index()其实就是咱们以前定义的切点,表示在哪里切入AOP。
接下来咱们要定义个切面,也就是所谓的日志功能的类。
package com.tengj.demo.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component //注入依赖
@Aspect //该注解标示该类为切面类
public class LogAspect {
@Pointcut("execution(* com.tengj.demo.service.impl.UserServiceImpl.*(..))")
public void logAop(){}
@Before("logAop() && args(name)")
public void logBefore(String name){
System.out.println(name+"前置通知Before");
}
@AfterReturning("logAop()")
public void logAfterReturning(){
System.out.println("返回通知AfterReturning");
}
@After("logAop() && args(name)")
public void logAfter(String name){
System.out.println(name+"后置通知After");
}
@AfterThrowing("logAop()")
public void logAfterThrow(){
System.out.println("异常通知AfterThrowing");
}
}复制代码
上面就是切面类的代码,很简单,这里用到了前面提的通知的几种类型。
这样就能实现切入功能了
@Pointcut("execution(* com.tengj.demo.service.impl.UserServiceImpl.*(..))")
public void logAop(){}复制代码
这里的@Pointcut注解是为了定义切面内重用的切点,也就是说把公共的东西抽出来,定义了任意的方法名称logAop,这样下面用到的各类类型通知就只要写成
@Before("logAop() && args(name)")
@AfterReturning("logAop()")
@AfterThrowing("logAop()")复制代码
这样既可,不然就要写成
@Before("execution(* com.tengj.demo.service.impl.UserServiceImpl.*(..))")
@AfterReturning("execution(* com.tengj.demo.service.impl.UserServiceImpl.*(..))")
@AfterThrowing("execution(* com.tengj.demo.service.impl.UserServiceImpl.*(..))")复制代码
你们是否注意到了@Before("logAop() && args(name)")
这里多出来个&& args(name)
,这个是用来传递参数的,定义只要跟sayHello参数名称同样就能够。
若是就此止步的话,LogAspect只会是Spring容器中的一个Bean,即使使用了AspectJ注解,但它并不会被视为切面,这些注解不会解析,也不会建立将其转换为切面的代理。
因此须要在XML里面配置一下,须要使用Spring aop命名空间中的<aop:aspectj-autoproxy/>
元素,简单以下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" default-lazy-init="true">
<context:component-scan base-package="com.tengj.demo"/>
<mvc:resources location="/WEB-INF/pages/" mapping="/pages/**"/>
<!-- 默认的注解映射的支持 -->
<mvc:annotation-driven/>
<!--启用AspectJ自动代理-->
<aop:aspectj-autoproxy/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>复制代码
接着就能够启动工程,访问index这个方法,http://localhost:8080/SpringMVCMybatis/test/view
执行结果:
tengj前置通知Before
hello,tengj
tengj后置通知After
返回通知AfterReturning复制代码
根据前面学的咱们知道,除了上面提到的通知外,还有一个更强大通知类型,就是环绕通知。能够自定义咱们须要切入的位置,能够替代上面提到的全部通知。看例子:
@Around("logAop()")
public void logAround(ProceedingJoinPoint jp){
try {
System.out.println("自定义前置通知Before");
jp.proceed();//将控制权交给被通知的方法,也就是执行sayHello方法
System.out.println("自定义后置通知After");
} catch (Throwable throwable) {
System.out.println("异常处理~");
throwable.printStackTrace();
}
}复制代码
执行结果:
自定义前置通知Before
hello,tengj
自定义后置通知After复制代码
这里主要是经过ProceedingJoinPoint这个参数。其中里面的proceed()方法就是将控制权交给被通知的方法。若是你忘记调用这个方法,那么你的通知实际上会阻塞对被通知方法的调用。
有意思的是,你能够不调用proceed()方法,从而阻塞堆被通知方法的访问,与之相似,你也能够在通知中对它进行屡次调用。要这样作的一个场景就是实现重试逻辑,也就是在被通知方法失败后,进行重复尝试。
这里介绍使用XML配置的方式来实现,在Spring的aop命名空间中,提供了多个元素用来在XML中声明切面。
AOP配置元素 | 用 途 |
---|---|
<aop:advisor> |
定义AOP通知器 |
<aop:after> |
定义AOP后置通知(无论被通知的方法是否执行成功) |
<aop:after-returning> |
定义AOP返回通知 |
<aop:after-throwing> |
定义AOP异常通知 |
<aop:around> |
定义AOP环绕通知 |
<aop:aspect> |
定义一个切面 |
<aop:aspectj-autoproxy> |
启用@AspectJ注解驱动的切面 |
<aop:before> |
定义一个AOP前置通知 |
<aop:config> |
顶层的AOP配置元素,大多数的<aop:*> 元素必须包含在<aop:config> 元素内 |
<aop:declare-parents> |
以透明的方式为被通知的对象引入额外的接口 |
<aop:pointcut> |
定义一个切点 |
咱们已经看过了<aop:aspectj-autoproxy/>
元素,它可以自动代理AspectJ注解的通知类。aop命名空间的其余元素可以让咱们直接在Spring配置中声明切面,而不须要使用注解。
因此,咱们从新来看看一下这个LogAspect类,此次咱们将它全部的AspectJ注解所有移除掉:
package com.tengj.demo.aspect;
public class LogAspect {
public void logBefore(String name){
System.out.println(name+"前置通知Before");
}
public void logAfterReturning(String name){
System.out.println("返回通知AfterReturning");
}
public void logAfter(String name){
System.out.println(name+"后置通知After");
}
public void logAfterThrow(String name){
System.out.println("异常通知AfterThrowing");
}
}复制代码
而后在xml配置文件中使用Spring aop命名空间中的一些元素,详细基本配置参考上面注解方式中的xml配置,这里是贴出来关键的代码:
<bean id="logAspect" class="com.tengj.demo.aspect.LogAspect" />
<aop:config>
<aop:aspect id="log" ref="logAspect">
<aop:pointcut id="logAop" expression="execution(* com.tengj.demo.service.impl.UserServiceImpl.sayHello(..)) and args(name)"/>
<aop:before method="logBefore" pointcut-ref="logAop"/>
<aop:after method="logAfter" pointcut-ref="logAop"/>
<aop:after-returning method="logAfterReturning" pointcut-ref="logAop"/>
<aop:after-throwing method="logAfterThrow" pointcut-ref="logAop"/>
<!--<aop:around method="logAfterThrow" pointcut-ref="logAop"/>-->
</aop:aspect>
</aop:config>复制代码
配置也 很好理解
<aop:config>
里面<aop:aspect>
一个切面,指向具体的bean类。<aop:pointcut>
定义切点,基本跟注解的很像,其中要注意的是xml配置里面若是要带参数的,用的再也不是&&,要使用and关键字才行(由于在XML中,“&”符号会被解析为实体的开始)执行效果以下:
tengj前置通知Before
hello,tengj
tengj后置通知After
返回通知AfterReturning复制代码
环绕通知也很简单,直接贴代码:
xml配置:
<aop:around method="logAround" pointcut-ref="logAop"/>复制代码
切面方法:
public void logAround(ProceedingJoinPoint jp,String name){
try {
System.out.println(name+"自定义前置通知Before");
jp.proceed();
System.out.println(name+"自定义后置通知After");
} catch (Throwable throwable) {
System.out.println("异常处理~");
throwable.printStackTrace();
}
}复制代码
执行结果:
tengj自定义前置通知Before
hello,tengj
tengj自定义后置通知After复制代码
Spring AOP是Spring学习中最关键的,我总结的这2种写法也是开发中最经常使用的。也不知道你们能不能理解~看得时候若是有不懂的地方能够提出来,我好修改一下,让更多的人理解并掌握AOP,但愿对你有所帮助。
一直以为本身写的不是技术,而是情怀,一篇篇文章是本身这一路走来的痕迹。靠专业技能的成功是最具可复制性的,但愿个人这条路能让你少走弯路,但愿我能帮你抹去知识的蒙尘,但愿我能帮你理清知识的脉络,但愿将来技术之巅上有你也有我。
订阅博主微信公众号:嘟爷java超神学堂(javaLearn)三大好处: