1、概述
- AOP,面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
- 减少重复代码,提高开发效率,便于后期维护
-
AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring 通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
-
AOP 相关术语:
- AOP 步骤:
- 编写核心业务代码(目标类的目标方法),明确切入点
- 把公用代码抽取出来,制作成通知(增强功能方法) 通知
- 在配置文件或使用注解中,声明切入点与通知间的关系,即切面
- 在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式:
1、当 bean 实现接口时,会用 JDK 代理模式。
2、当 bean 没有实现接口,用 cglib 实现。
3、可以强制使用 cglib(在 Spring 配置文件中加入<aop:aspectj autoproxy proxyt-target-class=”true”/>
)。
2、XML 方式 AOP
2.1、举例
- 步骤:
- 1、引入 Maven 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.8</version>
<scope>test</scope>
</dependency>
Spring AOP 的依赖是spring-aop
,spring-context
中已经包括了spring-aop
。AspectJ 是基于 Java 的 AOP 框架,是一个完全可以独立于 Spring 的 AOP 框架。Spring AOP 与 AspectJ 结合使用,使得整个 AOP 开发更加便捷。
- 2、创建目标类,明确切入点,即需要增强的方法。
public class MyTarget {
public void myTargetMethod() {
System.out.println("切入点,待增强的方法");
}
}
- 3、创建通知类,编写通知方法,即增强的逻辑。
public class MyAdvice {
public void myBeforeAdvice() {
System.out.println("前置增强(通知)");
}
}
- 3、目标类、通知类,交给 Spring 容器管理。
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="MyTarget" class="com.yscyber.spring.six.MyTarget"/>
<bean id="MyAdvice" class="com.yscyber.spring.six.MyAdvice"/>
</beans>
- 4、在 Spring 核心配置文件中,配置织入。
<?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 https://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="MyTarget" class="com.yscyber.spring.six.MyTarget"/>
<bean id="MyAdvice" class="com.yscyber.spring.six.MyAdvice"/>
<aop:config>
<!-- <aop:aspect ref=""> 指定通知类 -->
<aop:aspect ref="MyAdvice">
<!-- <aop:before method="" pointcut=""> 指定通知方法、需增强的方法 -->
<aop:before method="myBeforeAdvice" pointcut="execution(public void com.yscyber.spring.six.MyTarget.myTargetMethod())"/>
</aop:aspect>
</aop:config>
</beans>
- 5、测试。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class MyTest {
@Autowired
private MyTarget myTarget;
@Test
public void test1() {
myTarget.myTargetMethod();
}
}
/*
前置增强(通知)
切入点,待增强的方法
*/
2.2、切点表达式
-
<aop:before method="myBeforeAdvice" pointcut="execution(public void com.yscyber.spring.six.MyTarget.myTargetMethod())"/>
中的pointcut
定义切点表达式。切点表达式定义了哪些方法需要被增强。
- 切点表达式抽取,使得切点表达式能够复用,
<aop:pointcut id="", expression="">
<?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 https://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="MyTarget" class="com.yscyber.spring.six.MyTarget"/>
<bean id="MyAdvice" class="com.yscyber.spring.six.MyAdvice"/>
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(public void com.yscyber.spring.six.MyTarget.myTargetMethod())"/>
<!-- <aop:aspect ref=""> 指定通知类 -->
<aop:aspect ref="MyAdvice">
<!-- <aop:before method="" pointcut=""> 指定通知方法、需增强的方法 -->
<aop:before method="myBeforeAdvice" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
2.3、通知类型
Spring-30-
异常通知与后置通知,执行过程中只有1个生效。
-
环绕通知比较特殊,一般单独使用。一个环绕通知可以完全实现其他类型的通知。
public class MyTarget {
public void myTargetMethod() {
System.out.println("切入点,待增强的方法");
}
}
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
public void myAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
try {
System.out.println("前置通知");
// 执行被增强的方法,即 MyTarget.myTargetMethod
proceedingJoinPoint.proceed();
System.out.println("后置通知");
} catch (Throwable throwable) {
System.out.println("异常通知");
throwable.printStackTrace();
} finally {
System.out.println("最终通知");
}
}
}
<?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 https://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="MyTarget" class="com.yscyber.spring.six.MyTarget"/>
<bean id="MyAdvice" class="com.yscyber.spring.six.MyAdvice"/>
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(public void com.yscyber.spring.six.MyTarget.myTargetMethod())"/>
<aop:aspect ref="MyAdvice">
<aop:around method="myAroundAdvice" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class MyTest {
@Autowired
private MyTarget myTarget;
@Test
public void test1() {
myTarget.myTargetMethod();
}
}
/*
前置通知
切入点,待增强的方法
后置通知
最终通知
*/
3、注解方式 AOP
3.1、举例 — 部分注解
Spring-31 <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.8</version>
<scope>test</scope>
</dependency>
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect // @Aspect 通知类
public class MyAdvice {
@Before("execution(public void com.yscyber.spring.seven.MyTarget.myTargetMethod())")
public void myBeforeAdvice() {
System.out.println("前置通知");
}
}
import org.springframework.stereotype.Component;
@Component
public class MyTarget {
public void myTargetMethod() {
System.out.println("切入点,待增强的方法");
}
}
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.yscyber.spring.seven"/>
<!-- 相当于识别:@Aspect 等 AspectJ 注解 -->
<aop:aspectj-autoproxy/>
</beans>
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class MyTest {
@Autowired
private MyTarget myTarget;
@Test
public void test1() {
myTarget.myTargetMethod();
}
}
/*
前置通知
切入点,待增强的方法
*/
3.2、举例 — 完全注解
import org.springframework.stereotype.Component;
@Component
public class MyTarget {
public void myTargetMethod() {
System.out.println("切入点,待增强的方法");
}
}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect // @Aspect 通知类
public class MyAdvice {
@Before("execution(public void com.yscyber.spring.seven.MyTarget.myTargetMethod())")
public void myBeforeAdvice() {
System.out.println("前置通知");
}
}
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"com.yscyber.spring.seven"})
@EnableAspectJAutoProxy // 替代 XML 配置文件中的 <aop:aspectj-autoproxy/>
public class SpringConfig {
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {com.yscyber.spring.seven.SpringConfig.class})
public class MyTest {
@Autowired
private MyTarget myTarget;
@Test
public void test1() {
myTarget.myTargetMethod();
}
}
3.3、抽取切点表达式
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect // @Aspect 通知类
public class MyAdvice {
// 抽取切点表达式
@Pointcut("execution(public void com.yscyber.spring.seven.MyTarget.myTargetMethod())")
public void myPointcut() {}
@Before("myPointcut()")
public void myBeforeAdvice() {
System.out.println("前置通知");
}
}
网友评论