一、什么是AOP?
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用”横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
二、AOP核心概念
2.1.连接点 Joint point:
类里面那些可以被增强的方法,这些方法称之为连接点,表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point
2.2.切入点 Pointcut:
实际被增强的方法,称之为切入点,表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方
2.3.通知 Advice:
实际增强的逻辑部分称为通知 (增加的功能),Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
通知类型:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
2.4.目标对象 Target:被增强功能的对象(被代理的对象)
织入 Advice 的目标对象
2.5 切面Aspect: 表现为功能相关的一些advice方法放在一起声明成的一个Java类
Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
2.6 织入 Weaving:
创建代理对象并实现功能增强的声明并运行过程,将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
三、Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
- 默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
- 当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
AOP编程其实是很简单的事情,纵观AOP编程,只需要参与三个部分:
- 定义普通业务组件
- 定义切入点,一个切入点可能横切多个业务组件
- 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
四、spring中对于AOP的实现方法
AspectJ本身并不是spring框架中的组成部分, 是一个独立的AOP框架,一般把AspectJ和Spring框架的AOP依赖一起使用,所以要导入一个独立的依赖
4.1.方式一:通过注解的方式实现
4.1.1.导入依赖
创建Maven项目,导入依赖如下:
<?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>com.augus</groupId>
<artifactId>spring5_ioc01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
<!--spring切面 包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.22</version>
</dependency>
<!--织入包 spring-aspects 已经导入该包,这里可以不导入,有依赖导入-->
<!--<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>-->
<!--aop联盟包-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!--Apache Commons日志包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.1.2.切入表达式语法说明
切入点表达式: 通过一个表达式来确定AOP要增强的是哪个或者那些方法,语法结构:
execution([权限修饰符][返回值类型][类的全路径名][方法名](参数 列表) )
说明如下:
- execution(* com.augus.dao.UserDaoImpl.add(..)) //指定切点为UserDaoImpl.add方法
- execution(* com.augus.dao.UserDaoImpl.*(..)) //指定切点为UserDaoImpl.所有的方法
- execution(* com.augus.dao.*.*(..)) //指定切点为dao包下所有的类中的所有的方法
- execution(* com.augus.dao.*.add(..)) // 指定切点为dao包下所有的类中的add的方法
- execution(* com.augus.dao.*.add*(..)) // 指定切点为dao包下所有的类中的add开头的方法
4.1.3.基于注解方式实现项目结构
4.1.4.准备接口UserDao和EmpDao
package com.augus.dao;
public interface UserDao {
int addUser(Integer userid,String username,String password);
}
package com.augus.dao;
public interface EmpDao {
int addEmp(Integer empno,String ename,String job);
}
4.1.5.接口实现类
package com.augus.dao.impl;
import com.augus.dao.EmpDao;
import org.springframework.stereotype.Repository;
@Repository
public class EmpDaoImpl implements EmpDao {
@Override
public int addEmp(Integer empno, String ename, String job) {
System.out.println("*************这是EmpDaoImpl************");
return 1;
}
}
package com.augus.dao.impl;
import com.augus.dao.UserDao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao {
@Override
public int addUser(Integer userid, String username, String password) {
System.out.println("*************这是UserDaoImpl************");
return 1;
}
}
4.1.6.准备切面
- Join Point对象
Join Point对象封装了Spring A op中切面方法的信息,在切面方法中添加Join Point参数,就可以获取到封装了该方法信息的Join Point对象。常用api如下:
方法名 | 功能 |
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
- Proceeding Join Point对象
Proceeding Join Point对象是Join Point的子接口,该对象只用在@Around的切面方法中,添加了两个方法.
- Object proceed () throws Throw able //执行目标方法
- Object proceed(Object[] var1)throws Throw able //传入的新的参数去执行目标方法
需要注意的是
- 有多个增强类对同一个方法进行增强,通过@Order注解设置增强类优先级
- 数字越小,优先级越高
- 数字越小,其代理位置越靠近注入位置
package com.augus.asprct; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect public class DaoAspect { //定义公共的切点,匹配com.augus.dao这个包下所有add开头的方法进行增强 @Pointcut("execution(* com.augus.dao.*.add*(..))") public void addPointCut(){} //这个方法就像是给上面的这个切点起了一个别名一样,切点的名字就是方法名*/ /*前置通过:切点方法执行之前先执行的功能 参数列表可以用JoinPoint接收切点对象 可以获取方法值的参数*/ @Before("addPointCut()") public void methodBefore(JoinPoint joinPoint){ System.out.println("==============这是Before前置通知=============="); } /* 后置通知:方法执行之后要增强的功能 无论切点方法是否出现异常都会执行的方法 参数列表可以用于JoinPoint接受切点对象*/ @After("addPointCut()") public void methodAfter(){ System.out.println("==============这是After后置通知=============="); } /* 返回通知:切点方法正常运行结束后增强的功能 如果方法运行过程中出现异常,则该功能不允许 参数列表可以用于JoinPoint接受切点对象 可以用Object res 接受方法返回值 需要用returning指定返回值名称 */ @AfterReturning(value = "addPointCut()",returning = "res") public void methodAfterReturning(JoinPoint joinPoint,Object res){ System.out.println("==============这是AfterReturning返回通知=============="); } /* 异常通知:切点方法出现异常时运行的增强功能 如果方法没有出现异常,该功能不运行 参数列表可以用Exception ex 接收异常对象 需要通过throwing指定异常的名称 */ @AfterThrowing(value = "addPointCut()",throwing = "ex") public void methodAfterThrowing(Exception ex){ System.out.println("==============这是AfterThrowing异常通知=============="); } /* * 环绕通知:在切点方法之前和之后都进行功能增加 * 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能 * 方法列表可以通过ProceedingJoinPoint获取切点 * 通过proceedingJoinPoint.proceed()方法控制切点方法的执行位置 * proceedingJoinPoint.proceed()方法会将切点方法的返回值获取到,并交给我们,可以做后续处理 * 我们在环绕通知的最后需要将切点方法的返回值继续向上返回,否则切点方法在执行时接收不到返回值*/ @Around("addPointCut()") public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("==============这是Around前置=============="); Object proceed = proceedingJoinPoint.proceed(); System.out.println("==============这是Around后置=============="); return proceed; } }
4.1.7.准备配置文件
创建spring配置文件,名为applicationContext.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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> <!--开启spring包扫描--> <context:component-scan base-package="com.augus"/> <!--自动生成aop代理对象--> <aop:aspectj-autoproxy/> </beans>
4.1.8.测试文件
import com.augus.dao.EmpDao;
import com.augus.dao.UserDao;
import com.augus.dao.impl.UserDaoImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test1 {
@Test
public void testJoinPoint(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
/*UserDao userDao = context.getBean(UserDao.class);
int i = userDao.addUser(12, "zhangsanfeng", "wudang001");*/
EmpDao empDao = context.getBean(EmpDao.class);
int i = empDao.addEmp(20, "张雯", "测试");
System.out.println(i);
}
}
执行后结果如下:
4.1.9.完全使用注解开发
需要注意的是,上面虽然大量应用了注解,但是包扫描已经开启代理模式依然需要使用applicationContext.xml配置文件完成,我们也可以完成采用注解的方式开发,这样就直接不需要xml配置文件了,内容如下
准备配置类
package com.augus.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = "com.augus") //开启包扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) //开启代理模式
public class SpringConfig {
}
测试文件
import com.augus.config.SpringConfig;
import com.augus.dao.EmpDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test2 {
@Test
public void testJoinPoint(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
/*UserDao userDao = context.getBean(UserDao.class);
int i = userDao.addUser(12, "zhangsanfeng", "wudang001");*/
EmpDao empDao = context.getBean(EmpDao.class);
int i = empDao.addEmp(20, "张雯", "测试");
System.out.println(i);
}
}
4.2.方法二:通过XML方式实现
通过xml方式实现的时候,还是依赖之前的的几个类来完成,只不过 DaoAspect1 需要重新准备,不要在写注解,注解实现的功能有xml配置文件完成
4.2.1.DaoAspect1 切面准备
package com.augus.asprct; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class DaoAspect1 { /* * 前置通过:切点方法执行之前先执行的功能 * 参数列表可以用JoinPoint接收切点对象 * 可以获取方法值的参数*/ public void methodBefore(JoinPoint joinPoint){ System.out.println("***********这是Before前置通知*********"); } /* * 后置通知:方法执行之后要增强的功能 * 无论切点方法是否出现异常都会执行的方法 * 参数列表可以用于JoinPoint接受切点对象*/ public void methodAfter(){ System.out.println("***********这是After后置通知*********"); } /* * 返回通知:切点方法正常运行结束后增强的功能 * 如果方法运行过程中出现异常,则该功能不允许 * 参数列表可以用于JoinPoint接受切点对象 * 可以用Object res 接受方法返回值 需要用returning指定返回值名称 * */ public void methodAfterReturning(JoinPoint joinPoint, Object res){ System.out.println("***********这是AfterReturning返回通知*********"); } /* * 异常通知:切点方法出现异常时运行的增强功能 * 如果方法没有出现异常,该功能不运行 * 参数列表可以用Exception ex 接收异常对象 需要通过throwing指定异常的名称 * */ public void methodAfterThrowing(Exception ex){ System.out.println("***********这是AfterThrowing异常通知*********"); } /* * 环绕通知:在切点方法之前和之后都进行功能增加 * 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能 * 方法列表可以通过ProceedingJoinPoint获取切点 * 通过proceedingJoinPoint.proceed()方法控制切点方法的执行位置 * proceedingJoinPoint.proceed()方法会将切点方法的返回值获取到,并交给我们,可以做后续处理 * 我们在环绕通知的最后需要将切点方法的返回值继续向上返回,否则切点方法在执行时接收不到返回值*/ public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("************这是Around前置**************"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("************这是Around后置**************"); return proceed; } }
4.2.2.配置文件
<?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: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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--开启spring 包扫描-->
<context:component-scan base-package="com.augus"></context:component-scan>
<!--自动生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--创建对象-->
<bean id="userDao" class="com.augus.dao.impl.UserDaoImpl"></bean>
<bean id="empDao" class="com.augus.dao.impl.EmpDaoImpl"></bean>
<bean id="daoAspect" class="com.augus.asprct.DaoAspect1"></bean>
<!--在spring配置文件中配置切点-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcutAdd" expression="execution(* com.augus.dao.*.add*(..)))"/>
<!--配置切面-->
<aop:aspect ref="daoAspect">
<!--具体使用那种增强-->
<aop:before method="methodBefore" pointcut-ref="pointcutAdd"></aop:before>
<aop:after method="methodAfter" pointcut-ref="pointcutAdd"></aop:after>
<aop:around method="methodAround" pointcut-ref="pointcutAdd"></aop:around>
<aop:after-returning method="methodAfterReturning" pointcut-ref="pointcutAdd" returning="res"></aop:after-returning>
<aop:after-throwing method="methodAfterThrowing" pointcut-ref="pointcutAdd" throwing="ex"></aop:after-throwing>
</aop:aspect>
</aop:config>
</beans>
4.2.3.测试代码
注意:测试之前需要取消掉接口实现类上面的@Repository注解
import com.augus.dao.EmpDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test3 {
@Test
public void testJoinPoint(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
/*UserDao userDao = context.getBean(UserDao.class);
int i = userDao.addUser(12, "zhangsanfeng", "wudang001");*/
EmpDao empDao = context.getBean(EmpDao.class);
int i = empDao.addEmp(20, "张雯", "测试");
System.out.println(i);
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/253684.html