慢慢来比较快,虚心学技术
前言:DI (依赖注入)有助于应用对象之间的解耦,而 AOP(面向切面编程) 可以实现横切关注点与它们所影响的对象之间的解耦
一、什么是面向切面编程
Ⅰ、横切关注点:在软件开发中,散布于应用中多处的功能被称为横切关注点( cross-cutting concern )【比如说日志,安全和事务管理等】。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中)。把这些横切关注点与业务逻辑相分离正是面向切面编程( AOP )所要解决的问题。
Ⅱ、切面:横切关注点可以被模块化为特殊的类,这些类被称为切面( aspect )。
Ⅲ、AOP术语
①通知( Advice ):切面的工作被称为通知。通知描述切面的工作,同时决定切面何时工作【定义了切面工作做什么,什么时候做】
- 前置通知( Before ):在目标方法被调用之前调用通知功能;
- 后置通知( After ):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知( After-returning ):在目标方法成功执行之后调用通知;
- 异常通知( After-throwing ):在目标方法抛出异常后调用通知;
- 环绕通知( Around ):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
②连接点( Join point ):触发切面工作的点,比如方法执行,异常抛出等行为
③切点( Poincut ):决定切面工作的地方,比如某个方法等【定义了切面工作在哪里做】
④切面( Aspect ):通知和切点的结合【面】
⑤引入( Introduction ):允许我们向现有的类添加新方法或属性
⑥织入( Weaving ):把切面应用到目标对象并创建新的代理对象的过程【切面在指定的连接点被织入到目标对象中】
通知包含了需要用于多个应用对象的横切行为;连接点是程序执行过程中能够应用通知的所有点;切点定义了通知被应用的具体位置(在哪些连接点)。其中关键的概念是切点定义了哪些连接点会得到通知
Ⅳ、Spring对AOP的支持【 Spring AOP 构建在动态代理基础之上,因此, Spring 对 AOP 的支持局限于方法拦截。】
- 基于代理的经典 Spring AOP ;
- 纯 POJO 切面;
- @AspectJ 注解驱动的切面;
- 注入式 AspectJ 切面(适用于 Spring 各版本)。
二、面向切面编程实现
1、定义切点:
Spring支持通过AspectJ的切点表达式语言来定义 Spring 切面,同时增加通过bean的id指定bean的写法。
如:execution( com.my.spring.bean..(..))* 指定com.my.spring.bean包下所有类的所有方法作为切点
其结构解析如下:
AspectJ切点表达式的指示器不只有execution:
- arg() :限制连接点匹配参数为指定类型的执行方法
- execution() :用于匹配是连接点的执行方法
- this() :限制连接点匹配 AOP 代理的 bean 引用为指定类型的类
- target :限制连接点匹配目标对象为指定类型的类
- within() :限制连接点匹配指定的类型
各指示器之间可以通过&&(与),||(或),!(非)连接符进行连接实现多条件查询定义节点
如:execution(* com.my.spring.bean.* . *(..))&&arg(java.lang.Integer)
2.示例Demo
Spring AOP的实现依赖于spring-aop包和aspectjweaver包,需在pom文件引入:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
通过实例demo引入概念:
①定义一个基础接口类BaseInterface和实现类BaseBean
public interface BaseInterface {
/**
* 新增歌曲
*
* @param author 作者
* @param songTitle 歌曲名
*
* @return java.lang.Integer 返回当前歌曲总数
*
* @author xxx 2019/3/4
* @version 1.0
**/
Integer addSong(String author,String songTitle);
/**
* 删除歌曲
*
* @param author 作者
* @param songTitle 歌曲名
*
* @return java.lang.Integer 返回当前歌曲总数
*
* @author xxx 2019/3/4
* @version 1.0
**/
Integer delSong(String author,String songTitle);
}
@Component
public class BaseBean implements BaseInterface{
private String author;
private String songTitle;
private Integer count=0;
@Override
public Integer addSong(String author,String songTitle){
this.author = author;
this.songTitle = songTitle;
System.out.println("新增了一首歌:"+author+"-"+songTitle);
count++;
return count;
}
@Override
public Integer delSong(String author,String songTitle){
this.author = author;
this.songTitle = songTitle;
System.out.println("删除了一首歌:"+author+"-"+songTitle);
count--;
return count;
}
}
②创建一个切面类
@Aspect
@Component
public class BaseBeanAspect {
private Logger logger = LoggerFactory.getLogger(BaseBeanAspect.class);
/**
* 方法执行前的通知
*/
@Before("execution(* com.my.spring.bean.*.*(..))")
public void beforeInvoke(){
logger.debug("方法执行前");
}
/**
* 方法执行后的通知
*/
@After("execution(* com.my.spring.bean.*.*(..))")
public void afterInvoke(){
logger.debug("方法执行后");
}
/**
* 方法执行返回后的通知
*/
@AfterReturning("execution(* com.my.spring.bean.*.*(..))")
public void afterReturning(){
logger.debug("==================方法执行完成");
}
/**
* 方法抛出异常的通知
*/
@AfterThrowing("execution(* com.my.spring.bean.*.*(..))")
public void afterThrowing(){
logger.debug("==================方法执行报错");
}
}
③创建自动化装配的配置类
@Configuration
@EnableAspectJAutoProxy//开启自动代理开关,启用切面
@ComponentScan(basePackages = {"com.my.spring"})
public class ComponentConfig {
}
④测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ComponentConfig.class})
public class AppTest {
@Autowired
private BaseInterface baseInterface;
@Test
public void testBean(){
baseInterface.addSong("myBean","mySong");
baseInterface.delSong("myBean","mySong");
}
}
⑤测试结果
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行前
新增了一首歌:myBean-mySong
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行后
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法执行完成
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行前
删除了一首歌:myBean-mySong
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行后
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法执行完成
3、注解分析
前代码是使用注解实现SpringAOP的简单示例,我们了解以下其中用到的注解和实现:
Ⅰ、定义切面:@Aspect------------标识当前类是一个切面类(仅仅只是标识,我们可以看到该注解源码并没有使用@Component注解,所以使用该注解的bean依旧只是一个普通的POJO,使用时依旧需要显式或自动装配)
Ⅱ、定义切点:@PointCut
我们看到上述代码中在多个地方使用切点使用的是重复性的表达式,其实通过@PointCut注解定义切点,同时通过指定空方法名引入切点到各个通知即可
@Pointcut("execution(* com.my.spring.bean.*.*(..))")
public void pointCut(){//被用于标识的空方法
}
@Before("pointCut()")//以切点方法名引入
public void beforeInvoke(){
logger.debug("方法执行前");
}
Ⅱ、定义通知:
@Beafore(切点)-----------------切点方法执行前的通知
@After(切点)-------------------------切点方法执行后的通知
@AfterReturning(切点)-----------切点方法执行返回后的通知
@AfterThrowing(切点)------------切点方法抛异常后的通知
Ⅲ、开启自动代理:@EnableAspectJAutoProxy--------在配置类中使用,如果不启用的话,编写的切面将不生效
Ⅳ、定义环绕通知:@Around("pointCut()")
环绕通知是从方法执行前一直包裹直到方法执行完成后的一个通知,用的比较多,其中被定义的方法需要引入参数ProceedingJoinPoint,ProceedingJoinPoint对象封装了当前运行对象的具体信息,简单实现如下:
@Around("pointCut()")
public void aroundInvoke(ProceedingJoinPoint jp){
try {
logger.debug("=====================环绕执行方法开始");
Signature signature = jp.getSignature();
String methodName = signature.getName();
MethodSignature methodSignature = (MethodSignature) signature;
logger.debug("方法名:{}",methodName);
List<Object> args = Arrays.asList(jp.getArgs());
logger.debug("参数列表:{}",args);
Class<?> returnType = methodSignature.getMethod().getReturnType();
logger.debug("方法返回类型:{}",returnType);
Object proceed = jp.proceed();
logger.debug("======================环绕执行方法结束,方法执行结果:{}",proceed);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
其中,ProceedingJoinPoint对象源码分析如下:
JoinPoint
java.lang.Object[] getArgs()//获取连接点方法运行时的入参列表;
Signature getSignature() //获取连接点的方法签名对象;
java.lang.Object getTarget() //获取连接点所在的目标对象;
java.lang.Object getThis() //获取代理对象本身;
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法
java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法;
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
4、XML实现切面编程
我们知道使用注解实现切面编程是很方便直接的,但是有时候我们并不一定拥有通知类的源码,也就无法给对应的方法添加注解,这时候就需要使用XML配置实现了。XML配置实现与注解实现十分类似,我们可以看一下基本的实现节点:
①定义切面:<aop:aspect ref="切面类在xml文件中对应bean的id">
②定义切点:<aop:pointcut id="切点id" expression="切点表达式"/>
③定义通知:
<aop:beafore method="通知方法名" pointcut-ref="切点id"/>-------------定义方法执行前的通知
<aop:after method="通知方法名" pointcut-ref="切点id"/>-------------定义方法执行后的通知
<aop:afterReturning method="通知方法名" pointcut-ref="切点id"/>-------------定义方法执行返回后的通知
<aop:afterThrowing method="通知方法名" pointcut-ref="切点id"/>-------------定义方法执行抛出异常后的通知
<aop:around method="通知方法名" pointcut-ref="切点id"/>-------------定义方法环绕通知
④开启自动代理:<aop:aspectj-autoproxy/>
⑤表示aop配置:<aop:config></aop:config>
除了开启自动代理,aop的所有节点都需要包含在<aop:config></aop:config>节点中,如下demo演示:
applicaiton.xml
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启自动代理-->
<aop:aspectj-autoproxy/>
<!--装配基本类-->
<bean class="com.my.spring.bean.BaseBean" id="baseBean" name="baseBean"/>
<!--装配切面类-->
<bean class="com.my.spring.aspect.BaseBeanAspect" id="baseBeanAspect"/>
<!--aop配置-->
<aop:config>
<!--配置切面-->
<aop:aspect ref="baseBeanAspect">
<!--定义切点-->
<aop:pointcut id="pointCut" expression="execution(* com.my.spring.bean.*.*(..))"/>
<!--定义前置通知-->
<aop:before method="beforeInvoke" pointcut-ref="pointCut"/>
<!--定义后置通知-->
<aop:after method="afterInvoke" pointcut-ref="pointCut"/>
<!--定义方法执行返回后通知-->
<aop:after-returning method="afterReturning" pointcut-ref="pointCut"/>
<!--定义方法异常后通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut"/>
<!--定义方法环绕通知通知-->
<aop:around method="aroundInvoke" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
将BaseBean和BaseBeanAspect类中的注解去除,编写测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application.xml"})//将配置文件作为装配环境
public class AppXMLTest {
@Autowired
private BaseInterface baseInterface;
@Test
public void testBean(){
baseInterface.addSong("Mr D","The World!!");
baseInterface.delSong("Mr D","The World!!");
}
}
测试结果:
2019-03-04 22:07:37.901 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行前
2019-03-04 22:07:37.903 DEBUG com.my.spring.aspect.BaseBeanAspect - =====================环绕执行方法开始
2019-03-04 22:07:37.907 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法名:addSong
2019-03-04 22:07:37.910 DEBUG com.my.spring.aspect.BaseBeanAspect - 参数列表:[Mr D, The World!!]
2019-03-04 22:07:37.910 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法返回类型:class java.lang.Integer
2019-03-04 22:07:37.910 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行前
新增了一首歌:Mr D-The World!!
2019-03-04 22:07:37.911 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法执行完成
2019-03-04 22:07:37.911 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行后
2019-03-04 22:07:37.911 DEBUG com.my.spring.aspect.BaseBeanAspect - ======================环绕执行方法结束,方法执行结果:1
2019-03-04 22:07:37.912 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法执行完成
2019-03-04 22:07:37.912 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行后
2019-03-04 22:07:37.915 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行前
2019-03-04 22:07:37.915 DEBUG com.my.spring.aspect.BaseBeanAspect - =====================环绕执行方法开始
2019-03-04 22:07:37.916 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法名:delSong
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 参数列表:[Mr D, The World!!]
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法返回类型:class java.lang.Integer
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行前
删除了一首歌:Mr D-The World!!
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法执行完成
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行后
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - ======================环绕执行方法结束,方法执行结果:0
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法执行完成
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法执行后