一、前言
-
参考视频:遇见狂神说-Spring5
-
参考文档:Spring5官方文档
-
使用:
- IDEA 2019.3
- Maven
- Junit 4.12
二、什么是AOP?
AOP(Aspect Oriented Programming)意为:面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合降低,提高程序的可复用性,同时提高开发效率。
三、AOP在Spring中的作用
提供声明式事务;允许用户自定义切面。
下面是一些名词:
- 横切关注点:跨越应用程序多个模块的方法或功能。即,与业务逻辑无关,但是需要关注的部分,如:日志、安全、缓存、事务等......
- 切面(Aspect):横切关注点被模块化的特殊对象,即,一个类。比如:Log类。
- 通知(Advice):切面必须完成的工作,即,类中的一个方法。比如:Log类中的方法。
- 目标(Target):被通知对象。比如:代理对象中的抽象角色(接口类)。
- 代理(Proxy):向目标对象应用通知之后创建的对象。比如:代理类。
- 切入点(PointCut):切面通知执行的“地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
在SpringAOP中,通过Advice定义横切逻辑,Spring中支持5中类型的Advice:
通知类型 | 连接点 | 实现接口 |
---|---|---|
前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
后置通知 | 方法后 | org.springframework.aop.MethodAfterAdvice |
环绕通知 | 方法前后 | org.aopalliance.intercept.MethodInterceptor |
异常抛出通知 | 方法抛出异常 | org.springframework.aop.ThrowsAdvice |
引介通知 | 类中增加新的方法属性 | org.springframework.aop.IntroductionInterceptor |
四、maven依赖
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!--JUnit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--AOP织入-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
五、Service
路径
代码
这里用System.out.println("使用了XXX方法");
去代表Service层的动作:
Service:
package com.duzhuan.service;
/**
* @Autord: HuangDekai
* @Date: 2020/10/11 8:43
* @Version: 1.0
* @since: jdk11
*/
public interface Service {
void add();
void delete();
void update();
void query();
}
ServiceImpl:
package com.duzhuan.service;
/**
* @Autord: HuangDekai
* @Date: 2020/10/11 8:45
* @Version: 1.0
* @since: jdk11
*/
public class ServiceImpl implements Service {
@Override
public void add() {
System.out.println("使用了add()方法");
}
@Override
public void delete() {
System.out.println("使用了delete()方法");
}
@Override
public void update() {
System.out.println("使用了update()方法");
}
@Override
public void query() {
System.out.println("使用了query()方法");
}
}
六、前后Advice实现
路径
代码
BeforeLog:
package com.duzhuan.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* @Autord: HuangDekai
* @Date: 2020/10/11 8:50
* @Version: 1.0
* @since: jdk11
*/
public class BeforeLog implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("[DEBUG]"+method.getClass().getName()+"执行了: "+method.getName());
}
}
AfterLog:
package com.duzhuan.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
/**
* @Autord: HuangDekai
* @Date: 2020/10/11 8:52
* @Version: 1.0
* @since: jdk11
*/
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("[DEBUG]"+method.getClass().getName()+"执行了: "+method.getName()+" 返回值为: "+returnValue);
}
}
补充说明
如果有了解过动态代理的话,可以很快地知道public void before(Method method, Object[] args, Object target)
中,args是被代理的方法的输入参数,target是被代理的接口。Spring5入门-10-静态代理与动态代理略有提及。
七、实现织入
7.1 使用Spring的API接口
路径
代码
<?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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="serviceImpl" class="com.duzhuan.service.ServiceImpl"/>
<bean id="beforeLog" class="com.duzhuan.log.BeforeLog"/>
<bean id="afterLog" class="com.duzhuan.log.AfterLog"/>
<aop:config>
<!--execution()执行器 具体操作可以自行查询 这里第一个*代表返回值的类型是所有类型
第二个*代表ServiceImpl下的所有方法(..)表示所有的输入参数-->
<aop:pointcut id="servicePointCut" expression="execution(* com.duzhuan.service.ServiceImpl.*(..))"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="servicePointCut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="servicePointCut"/>
</aop:config>
</beans>
aop:pointcut
,aop的切入点,aop:advisor
则是执行的advisor。
7.2 测试样例
路径
代码
package com.duzhuan.service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Autord: HuangDekai
* @Date: 2020/10/11 9:27
* @Version: 1.0
* @since: jdk11
*/
public class ServiceTest {
@Test
public void serviceImplTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 注意,由于aop是用动态代理实现,此处必须使用 接口 才能实现织入
Service serviceImpl = context.getBean("serviceImpl", Service.class);
serviceImpl.add();
serviceImpl.delete();
serviceImpl.update();
serviceImpl.query();
}
}
结果
7.3 自定义实现AOP
路径
配置文件
<?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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="serviceImpl" class="com.duzhuan.service.ServiceImpl"/>
<bean id="beforeLog" class="com.duzhuan.log.BeforeLog"/>
<bean id="afterLog" class="com.duzhuan.log.AfterLog"/>
<!--方法一: 使用原生Spring API接口-->
<!-- <aop:config>
<!–execution()执行器 具体操作可以自行查询 这里第一个*代表返回值的类型是所有类型
第二个*代表ServiceImpl下的所有方法(..)表示所有的输入参数–>
<aop:pointcut id="servicePointCut" expression="execution(* com.duzhuan.service.ServiceImpl.*(..))"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="servicePointCut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="servicePointCut"/>
</aop:config>-->
<!--方法二: 自定义类-->
<bean id="logDiyPointCut" class="com.duzhuan.diyPointCut.LogDiyPointCut"/>
<aop:config>
<aop:aspect ref="logDiyPointCut">
<aop:pointcut id="servicePointCut" expression="execution(* com.duzhuan.service.ServiceImpl.*(..))"/>
<aop:before method="beforeAdvice" pointcut-ref="servicePointCut"/>
<aop:after method="afterAdvice" pointcut-ref="servicePointCut"/>
</aop:aspect>
</aop:config>
</beans>
执行测试样例
7.4 注解实现
路径
代码
LogAnnotationPointCut
package com.duzhuan.diyPointCut;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @Autord: HuangDekai
* @Date: 2020/10/11 10:53
* @Version: 1.0
* @since: jdk11
*/
@Aspect
public class LogAnnotationPointCut {
@Before("execution(* com.duzhuan.service.ServiceImpl.*(..))")
public void beforeAdvice(){
System.out.println("===============!方法执行前!===============");
}
@After("execution(* com.duzhuan.service.ServiceImpl.*(..))")
public void afterAdvice(){
System.out.println("===============!方法执行后!===============");
}
}
执行测试样例