一.aop的基本概念
面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。就是把程序中重复的功能抽取出来,例如性能监控、日志记录、权限控制这些公共逻辑,通过切面编程的方式把功能添加到原有的业务代码中,达到不修改原有代码对功能进行增强的目的
二.aop的实现方式
1.Aspect j:静态实现
编译期间植入增强逻辑,即编译号class文件中已经包含了增强逻辑
2.Spring aop:动态实现
在程序运行期间使用动态代理来增强代码功能
三.优势
不修改原码对功能进行增强
减少重复代码
可维护性强
四.aop相关的概念
1.pointout:切入点,用来描述spring aop要对安歇方法进行拦截
2.advice(通知):拦截到方法后要做的事,就是通知。
通知分为5类:前置通知、后置返回通知、异常通知、最终通知、环绕通知
3.切面:切入点和通知的结合
五.配置uersService类
1.新建uersService.java文件
package com.hhh.proxy;
public class UersService implements IUserservice{
public void addUser(){
System.out.println("add user success");
}
}
2.新建接口IUsersSvice.java文件
package com.hhh.proxy;
public interface IUserservice {
public void addUser();
}
六.spring aop的使用步骤
1.导入spring aop相关的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
还依赖了Aspect j
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
刷新工程
2.制作通知类
2.1新建一个通知类,名为UserServiceAdvice.java,在里头添加代码如下:
package com.hhh.proxy;
//通知类
public class UserServiceAdvice {
//在目标执行之前会被执行
public void beforeAdvice(){
System.out.println("前置通知");
}
//在目标方法成功返回之后被执行
public void afterReturnAdvice(){
System.out.println("后置返回通知");
}
//在目标方法发生异常后执行
public void exceptionAdvice(){
System.out.println("异常通知");
}
//在目标方法结束后执行,发生异常也会执行(类似try catch中的finally)
public void afterAdvice(){
System.out.println("最终通知");
}
//自己手动控制方法的执行时机
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("环绕通知");
}
}
3.配置aop(在spring的配置文件进行配置,applicationContext.xml)
3.1配置一个bean
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- 配置bean -->
<bean id="uersService" class="com.hhh.proxy.UersService"></bean>
<!--aop的配置-->
<!--把通知类放到spring容器中-->
<bean id="uersServiceAdvice" class="com.hhh.proxy.UserServiceAdvice"/>
<!--开始aop的配置,前提是找相关约束-->
<aop:config>
<!--切面(通知和切入点的结合)-->
<!--这里ref的属性值为,引用的通知类,即为上面所示id值-->
<aop:aspect ref="uersServiceAdvice">
<!--切入点的设置,id值随意-->
<!--
expression的值:需要定位到的方法
权限修饰符 返回值 包的路径 类的名字 方法名(参数列表)
-->
<aop:pointcut id="pt1" expression="execution(public void com.hhh.proxy.UersService.addUser())"/>
<!--具体的通知类型,方法之前增加这个逻辑,method属性值为通知类UserServiceAdvice中的方法-->
<!-- pointcut-ref切入点的引用:通过哪个切入点表达式去找,给哪个方法加前置通知-->
<aop:before method="beforeAdvice" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
</beans>
3.2aop的测试,新建一个test_aop.java,代码如下
package com.hhh.proxy;
import org.springframework.context.support.ClassPathXmlApplicationContext;
//aop的测试
public class Test_aop {
public static void main(String[] args) {
//创建容器
ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
//从容器中获取bean对象
//spring通过aop增强uersService的功能
//这里app.getBean中的参数为IUserservice.class是因为在UersService类中实现了接口.....(具体怎么样的也不是很清楚)
IUserservice uersService = app.getBean(IUserservice.class);
uersService.addUser();
}
}
3.3找aop的相关约束
网址:spring.io——>Projects——>Spring Framework——>LEARN——>(Documentation下的)Reference.Doc——>Core——>找到10.Appendix——>10.1.2.The aop Schema——>复制相关约束
4.运行代码
七.spring aop其他类型通知的设置
1.配置后置返回通知,可以获取到方法的返回值。
2.异常通知和最终通知配置(这时程序运行没有异常),在xml文件中配置最终通知时要放在后置返回通知的后面,位置影响运行顺序。
3.手动给UersService类造一个异常
当程序发生异常时,后置返回通知不不会显示,只有异常通知和最终通知(一定会执行)。运行效果如下
4.只配置环绕通知,执行效果:没有执行业务方法。
需要手动控制方法的执行时机
5.环绕通知更灵活的种一种方式,环绕通知的代码块。
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
try {
System.out.println("环绕前置通知");
//执行原有逻辑
Object proceed = proceedingJoinPoint.proceed();
//后置返回通知
} catch (Throwable e) {
e.printStackTrace();
System.out.println("环绕异常通知");
}finally {
//最终通知
System.out.println("环绕最终通知");
}
}
运行效果
发生异常的运行效果
八.aop的主要功能
通过配置的方式简化动态代理开发,达到不修改业务代码就能增强功能的目的
九.基于注解+xml配置spring的aop
1.配置目标类的bean
1.1给UersService类添上注解
1.2在applicationContext.xml中开启spring的注解扫描还有对aspect的支持
<!--开启spring的注解扫描-->
<context:component-scan base-package="com.hhh.proxy"/>
<!--开启spring对aspect的支持-->
<aop:aspectj-autoproxy/>
2.把通知类配置到容器里头
3.aop配置
3.1在xml中aop的配置由aop的标签配置,则根据注解在通知类中添上
3.2切入点表达式的定义
3.3前置通等通知也是根据注解完成配置,UserServiceAdvice.java完整代码如下
package com.hhh.proxy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//通知类
@Component
@Aspect
public class UserServiceAdvice {
//定义切入点
//切入点表达式
//权限修饰符 返回值类型 包名(a.b.c)类名 方法名(参数列表)
@Pointcut("execution(public void com.hhh.proxy.IUserservice.addUser())")
public void pt1(){
}
@Before("pt1()")
//在目标执行之前会被执行
public void beforeAdvice(){
System.out.println("前置通知");
}
@AfterReturning("pt1()")
//在目标方法成功返回之后被执行
public void afterReturnAdvice(){
System.out.println("后置返回通知");
}
@AfterThrowing("pt1()")
//在目标方法发生异常后执行
public void exceptionAdvice(){
System.out.println("异常通知");
}
@After("pt1()")
//在目标方法结束后执行,发生异常也会执行(类似try catch中的finally)
public void afterAdvice(){
System.out.println("最终通知");
}
}
3.4程序执行
4.可以通过通配符*将匹配其他类中的其他方法,增强其功能
4.1如增强OrderService类中的方法
@Pointcut("execution(public * com.hhh.proxy.*.*(..))")
4.2运行报错,后续找着问题了再回来修改
十.在aop通知中获取方法的参数
1.将IUserservice.java文件中的方法修改成有参数的。代码如下。
package com.hhh.proxy;
public interface IUserservice {
public String addUser(String username);
}
2.也将UersService.java文件中的方法修改成有参数的。代码如下。
package com.hhh.proxy;
import org.springframework.stereotype.Component;
@Component
public class UersService implements IUserservice{
public String addUser(String username){
System.out.println("add user"+username+ "success");
return "success";
//int a=1/0;
}
}
3.先修改UserServiceAdvice.java文件中(前置通知方法添加JoinPoint point参数)前置通知的代码来获取参数。其中JoinPoint作为通知方法的第一个参数,让spring给其赋值,通过point.getArgs()来获取目标方法的参数列表,代码如下
@Before("pt1()")
//在目标执行之前会被执行
//注解下的aop通知中获取方法的参数和返回值
//加入参数JoinPoint point
public void beforeAdvice(JoinPoint point){
//通过point来获取方法的参数
Signature signature = point.getSignature();
System.out.println("前置通知开始"+signature);
//输出参数列表
Object[] args = point.getArgs();
//判断参数是否为空
if(args!=null){
for (Object arg : args) {
System.out.println(arg);
}
}
System.out.println("前置通知结束"+signature);
}
其中还需要修改,切入点表达式中返回值类型和参数列表地方,代码如下
@Pointcut("execution(public * com.hhh.proxy.IUserservice.*(..))")
4.测试类Test_aop.java文件中的参数值添上,具体代码如下
package com.hhh.proxy;
import org.springframework.context.support.ClassPathXmlApplicationContext;
//aop的测试
public class Test_aop {
public static void main(String[] args) {
//创建容器
ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
//从容器中获取bean对象
//spring通过aop增强uersService的功能
//这里app.getBean中的参数为IUserservice.class是因为在UersService类中实现了接口.....(具体怎么样的也不是很清楚)
IUserservice uersService = app.getBean(IUserservice.class);
//添加参数
uersService.addUser("aaaa ");
}
}
5.测试类运行效果如下
6.前置、后置返回、最终、异常通知里头均可使用上述方法得到参数列表
十一.在aop通知中获取方法的返回值
1.只能在后置返回通知中获取方法的返回值,修改后置返回通知方法中的代码,基于注解开发,给注解添加Returning属性,指定方法的参数名,在方法上接收参数,代码如下
//在后置返回通知中获取方法的返回值
//参数中添加return
@AfterReturning(value = "pt1()",returning = "returnValue")
//在目标方法成功返回之后被执行
public void afterReturnAdvice(JoinPoint point,String returnValue){
System.out.println("后置返回通知开始");
//输出参数列表
Object[] args = point.getArgs();
//判断参数是否为空
if(args!=null){
for (Object arg : args) {
System.out.println(arg);
}
}
System.out.println("后置返回通知结束,返回值:"+returnValue);
}
2.运行程序,效果如下
3.在环绕通知中获取方法的返回值,先将其他通知的注解注释,只留环绕通知,环绕通知通过手动执行方法,获取返回值。环绕通知方法中的代码如下
@Around("pt1()")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
try {
System.out.println("环绕前置通知");
Object[] args = proceedingJoinPoint.getArgs();
//判断参数是否为空
if (args != null) {
for (Object arg : args) {
System.out.println(arg);
}
}
//执行原有逻辑
Object proceed = proceedingJoinPoint.proceed();
//后置返回通知
System.out.println("后置返回通知,返回值:" + proceed);
} catch (Throwable e) {
e.printStackTrace();
System.out.println("环绕异常通知");
}finally {
//最终通知
System.out.println("环绕追踪通知");
}
}
4.运行效果如下