AOP面向切面编程
概述:面向切面编程从动态角度考虑程序运行的过程。AOP底层,就是采用动态代理模式实现的,采用了两种代理:JDK的动态代理,与CGLIB的动态代理。AOP可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。
面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码。
Spring支持的AOP实现
Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
After通知:在目标方法被调用后调用,涉及接口org.springframework.aop.AfterReturningAdvice;
Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;
Rround通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。
AOP常用的术语
切面:就是重复的,公共的,通用的功能称为切面,例如:日志,事务,权限
连接点:就是目标方法,因为在目标方法中要实现目标方法的功能和切面功能
切入点:多个连接点构成切入点,切入点可以是一个目标方法,也可以是一个类中的所有方法,可以是某个包下的所有类的方法
目标对象:操作谁,谁就是
通知:来指定切入的时机,是在目标方法执行前还是执行后,或者出错时,还是环绕目标方法切入切面功能。
AspectJ框架
一个优秀的面向对面的框架,它扩展了java语言,提供强大的切面实现,基于java语言开发,所以能够无缝扩展,且框架易学易用
常用通知
前置通知@Before
后置通知@AfterReturning
环绕通知@Around
最终通知@After
定义切入点@Pointcut
AspectJ切入表达式(必须掌握)
规范的公式:
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化后的公式:
execution(方法返回值 方法声明(参数))
会用到的符号:
* 代表任意个任意字符
.. 如果出现在方法中,则代表任意参数;如果在路径中,则代表本路径极其所有的子路径
例如execution(public * * (..)) 代表公共权限下的任意返回值的任意方法的任意参数
AspectJ的前置通知(@Before)
在目标方法执行前切入切面功能,在切面方法中不可以获得目标方法的返回值,只能得到目标方法的签名( 访问权限 方法返回值 方法声明(参数))
pom.xml,添加依赖为项目作准备
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Spring_AspectJ</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Spring_AspectJ</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- Spring 依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- AspectJ 依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
创建业务类接口,创建方法
package ys.com.Before;
//业务接口
public interface SomeService {
default String doSome(String name,int age){return name;}
}
创建业务实现类,实现接口方法
package ys.com.Before;
//业务实现类
public class SomeServiceImpl implements SomeService{
@Override
public String doSome(String name, int age) {
System.out.println("前置目标方法被实现了");
return name+"今年:"+age;
}
}
创建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 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="someService" class="ys.com.Before.SomeServiceImpl"></bean>
<!-- 创建切面对象-->
<bean id="aop" class="ys.com.Before.Aop"></bean>
<!-- 绑定切面-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
主方法,输出
package ys.com.Main;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.Before.SomeService;
import ys.com.Before.SomeServiceImpl;
public class myApp {
public static void main(String[] args) {
// 启动Spring容器
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("Before/contxtUers.xml");
// 取出对象
SomeService service =(SomeService) applicationContext.getBean("someService");
String s =service.doSome("张三",22);
System.out.println(s);
}
}
JDK动态代理和CGLib动态代理的切换
Spring配置文件中,默认为JDK动态代理,此时只能用接口创建对象接受getBean值
下面是转化为CGLib动态代理---可以使用接口实现类对象,接口也是可以。
并将项目改为了用注解创建对象
对于上述代码修改,主方法,使用接口实现类创建对象
package ys.com.Main;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.Before.SomeService;
import ys.com.Before.SomeServiceImpl;
public class myApp {
public static void main(String[] args) {
// 启动Spring容器
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("Before/contxtUers.xml");
// 取出对象
SomeServiceImpl service =(SomeServiceImpl) applicationContext.getBean("someService");
String s =service.doSome("张三",22);
System.out.println(s);
}
}
再修改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"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 创建业务对象-->
<!-- <bean id="someServiceImpl" class="ys.com.Before.SomeServiceImpl"></bean>-->
<!-- 创建切面对象-->
<!-- <bean id="aop" class="ys.com.Before.Aop"></bean>-->
<!-- 包扫描,检索注解-->
<context:component-scan base-package="ys.com.Before"></context:component-scan>
<!-- 绑定切面-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
对切面方法添加参数,可以获取目标方法的签名和参数
package ys.com.Before;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//切面类,包含切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component //使用注解由Spring创建对象
public class Aop implements SomeService{
// 所有的切面的功能都是由切面方法来实现的
// 前置通知的切面方法规范
// 1、访问权限是public
// 2、方法无返回值(void)
// 3、方法名称自定义
// 4、方法没有参数,如果有只能是JoinPoint类型
// 5、必须是有@Before注解来声明切入的时机和切入点
// 参数 value:指定切入点表达式
@Before("execution(String doSome(String,int))")
public void myBefore(){
System.out.println("前置通知功能实现了");
}
@Before("execution(String doSome(String,int))")
public void joinPoint(JoinPoint jp){
System.out.println("目标方法的签名:"+jp.getSignature());
// jp.getArgs()是数组的形式 使用Arrays.toString可以输出
System.out.println("目标方法的参数:"+ Arrays.toString(jp.getArgs()));
}
}
package ys.com.Before;
import org.springframework.stereotype.Service;
//业务实现类
@Service //使用注解由Spring创建对象
public class SomeServiceImpl implements SomeService{
@Override
public String doSome(String name, int age) {
System.out.println("前置目标方法被实现了");
return name+"今年:"+age;
}
}
AspectJ的后置通知(@AfterReturning)
在目标方法执行后切入切面功能,在切面方法中可以获得目标方法的返回值,可以修改目标方法的返回值,但是目标方法的返回值不能为八大基本类型和String,引用类型可以被修改。
创建接口实现类
package ys.com.AfterReturning;
import org.springframework.stereotype.Component;
@Component
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
创建接口
package ys.com.AfterReturning;
//业务接口
public interface SomeService {
default String doSome(String name,int age){return name;}
public Student doStudent();
}
创建接口实现类
package ys.com.AfterReturning;
import org.springframework.stereotype.Service;
//业务实现类
@Service //使用注解由Spring创建对象
public class SomeServiceImpl implements SomeService {
@Override
public String doSome(String name, int age) {
System.out.println("后置目标方法被实现了");
return name+"今年:"+age;
}
@Override
public Student doStudent() {
System.out.println("学生类");
return new Student("张三");
}
}
创建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"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 创建业务对象-->
<!-- <bean id="someServiceImpl" class="ys.com.Before.SomeServiceImpl"></bean>-->
<!-- 创建切面对象-->
<!-- <bean id="aop" class="ys.com.Before.Aop"></bean>-->
<!-- 包扫描,检索注解-->
<context:component-scan base-package="ys.com.AfterReturning"></context:component-scan>
<!-- 绑定切面-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
定义主方法
package ys.com.Main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.AfterReturning.SomeService;
import ys.com.AfterReturning.Student;
public class myApp {
public static void main(String[] args) {
// 启动Spring容器
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("AfterReturning/contxtUers.xml");
// 取出对象
SomeService service =(SomeService) applicationContext.getBean("someServiceImpl");
Student s=service.doStudent();
System.out.println("主方法中的返回值为"+s);
}
}
输出结果,在接口实现类的重写方法中,返回值为“张三”,通过切面方法修改后变为了“李四”
AspectJ的环绕通知(@Around)
通过拦截目标方法的方式,在目标方法前后增强功能的通知。是功能强大的通知,一般事务使用此通知。可以轻易的改变目标方法的返回值
接口方法
package ys.com.Around;
//业务接口
public interface SomeService {
String doSome(String name,int age);
}
接口实现类
package ys.com.Around;
import org.springframework.stereotype.Service;
//业务实现类
@Service //使用注解由Spring创建对象
public class SomeServiceImpl implements SomeService {
@Override
public String doSome(String name, int age) {
System.out.println("目标方法被实现了"+name+"今年:"+age);
return "abcd";
}
}
切面方法
package ys.com.Around;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Locale;
//切面类,包含切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component //使用注解由Spring创建对象
public class Aop{
// 环绕通知的规范
// 访问权限是public
// 切面方法有返回值,此返回值就是目标方法的返回值
// 方法名自定义
// 方法有参数,此参数就是目标方法
// 回避异常
// 使用@Around注解声明是环绕通知
// value:指定切入点表达式
@Around(value = "execution(String doSome(..))")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// ProceedingJoinPoint 函数的参数类型
// 前切功能
System.out.println("前");
// 目标方法
// .proceed调用函数 .getArgs()获取所有参数类型的的函数,包括无参
Object object=proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
// 后切功能
System.out.println("后");
return object.toString().toUpperCase(Locale.ROOT);
// toString()强转化类型为字符串 toUpperCase将字符串的内容大写
}
}
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"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 创建业务对象-->
<!-- <bean id="someServiceImpl" class="ys.com.Before.SomeServiceImpl"></bean>-->
<!-- 创建切面对象-->
<!-- <bean id="aop" class="ys.com.Before.Aop"></bean>-->
<!-- 包扫描,检索注解-->
<context:component-scan base-package="ys.com.Around"></context:component-scan>
<!-- 绑定切面-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
主方法
package ys.com.Main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.Around.SomeService;
public class myApp {
public static void main(String[] args) {
// 启动Spring容器
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("Around/contxtUers.xml");
// 取出对象
SomeService service =(SomeService) applicationContext.getBean("someServiceImpl");
String s=service.doSome("张三",22);
System.out.println("主方法中的返回值为"+s);
}
}
AspectJ的最终通知(@After)
无论目标方法是否正常执行,最终通知都会被执行。
接口方法与接口实现类与前面相同(省略)
切面方法
package ys.com.After;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Locale;
//切面类,包含切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component //使用注解由Spring创建对象
public class Aop{
// 最终通知的规范
// 访问权限是public
// 方法没有返回值
// 方法名自定义
// 方法没有参数,如果有只能是JoinPoint
// 使用@After注解声明是最终通知
// value:指定切入点表达式
@After(value = "execution(String doSome(..))")
public void myAround() {
System.out.println("最终通知的功能输出......");
}
}
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"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 创建业务对象-->
<!-- <bean id="someServiceImpl" class="ys.com.Before.SomeServiceImpl"></bean>-->
<!-- 创建切面对象-->
<!-- <bean id="aop" class="ys.com.Before.Aop"></bean>-->
<!-- 包扫描,检索注解-->
<context:component-scan base-package="ys.com.After"></context:component-scan>
<!-- 绑定切面-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
主方法
package ys.com.Main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.After.SomeService;
public class myApp {
public static void main(String[] args) {
// 启动Spring容器
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("After/contxtUers.xml");
// 取出对象
SomeService service =(SomeService) applicationContext.getBean("someServiceImpl");
String s=service.doSome("张三",22);
System.out.println("主方法中的返回值为"+s);
}
}
当多个通知存在时,从上到下的输出顺序为
环绕通知的前置通知
前置通知
目标方法
环绕通知的后置通知
最终通知
后置通知
测试方法的输出
@Pointcut 定义切入点别名
当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护均较为麻烦。AspectJ提供了@Pointcut注解,用于定义 execution切入点表达式。其用法是,将@Pointcut注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut定义的切入点。这个使用@Pointcut注解的方法一般使用 private的标识方法,即没有实际作用的方法。
如果多个切面切入同一个切入点,可以使用别名简化开发
使用@Pointcut注解,创建一个空方法,此方法的名称就是别名。
将前置通知中的切面方法改造为
package ys.com.Pointcut;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//切面类,包含切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component //使用注解由Spring创建对象
public class Aop implements SomeService {
@Pointcut(value = "execution(String doSome(String,int))")
public void Pointcut(){}
@Before("Pointcut()")
public void myBefore(){
System.out.println("前置通知功能实现了");
}
@Before("Pointcut())")
public void joinPoint(JoinPoint jp){
System.out.println("目标方法的签名:"+jp.getSignature());
// jp.getArgs()是数组的形式 使用Arrays.toString可以输出
System.out.println("目标方法的参数:"+ Arrays.toString(jp.getArgs()));
}
}