Table of Contents
2.1:Aop场景:在系统执行方法是加日志来获取方法的参数;
5:当被代理类没有实现接口时,spring自动默认使用CGLIB代理、
6.1:当被代理类有实现接口时,默认使用JDK代理;在容器中保存的是代理对象,获取bean只能传接口类型,也可以用id来获取这个接口对象
三:AOP面向切面变成
1:AOP的介绍
AOP:(Aspect Oriented Programming)面向切面编程;
OOP:(Object Oriented Programming )面向对象编程;
面向切面编程:基于OOP基础之上新的编程思想;
指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式,面向切面编程;
2:AOP场景
2.1:Aop场景:在系统执行方法是加日志来获取方法的参数;
加日志记录:
1)、直接编写在方法内部;不推荐,修改维护麻烦;
日志记录:系统的辅助功能;
业务逻辑:(核心功能)
耦合;
2)、我们希望的是;
业务逻辑:(核心功能);日志模块;在核心功能运行期间,自己动态的加上;
运行的时候,日志功能可以加上;
可以使用动态代理来将日志代码动态的在目标方法执行前后先进行执行;
2.2:原生JDK动态代理实现上述场景
动态代理的内容我详细写了一篇博客:动态代理介绍
2.3:JDK动态代理的确定
3:Aop专业术语
将某段代码动态的切入(不把日志代码写死在业务逻辑方法中)到指定方法的指定位置)进行运行的这种编程方式(Spring简化了面向切面编程)
●AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。
●AOP编程操作的主要对象是切面(aspect),而切面模块化横切关注点。
●在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”。
●AOP的好处:
○每个事物逻辑位于一个位置,代码不分散,便于维护和升级
○业务模块更简洁,只包含核心业务代码
- 横切关注点:从每个方法中抽取出来的同一类非核心业务。如方法执行前输出日志;
- 切面(Aspect):封装横切关注点信息的类,每个关注点体现为一个通知方法。就是日志类
- 通知(Advice):切面必须要完成的各个具体工作
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的代理对象
- 连接点(Joinpoint):横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。
- 切入点(pointcut):定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
4:AOP的使用
4.1:导包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
</dependencies>
4.2:写配置
1:将目标类和切面类(封装了通知方法(在目标方法执行前后执行的方法))加入到ioc容器中
:2
2:还应该告诉Spring到底哪个是切面类@Aspect
4.3:AOP的五种注解
try { @Before():前置通知 在目标方法执行前执行 method.invoke(obj,args);//执行目标方法 @After() :后置通知 在目标方法执行后执行 } catch (Exception e) { @AfterThrowing() :后置异常通知 }finally { @AfterReturning() :后置成功通知 }
还有个环绕通知
@around
4.4:切面类代码
package com.wkl.utils;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Description:
* Date: 2020/6/27 - 上午 12:53
* author: wangkanglu
* version: V1.0
*/
@Component
@Aspect
public class LogUtil {
@Before("execution(public void com.wkl.impl.UserImpl.add())")
//方法开始执行前,写入切入点表达式
//execution(权限访问控制符 返回值类型 方法签名)
public void Start(){
System.out.println("start。。。。");
}
@After("execution(public void com.wkl.impl.UserImpl.add())")
//想在目标方法正常执行完成之后执行
public void logReturn(){
System.out.println("logReturn。。。。");
}
@AfterThrowing("execution(public void com.wkl.impl.UserImpl.add())")
//想在目标方法出现异常的时候执行
public void logException(){
System.out.println("logException。。。。");
}
@AfterReturning("execution(public void com.wkl.impl.UserImpl.add())")
//想在目标方法结束的时候执行
public void logEnd(){
System.out.println("logEnd。。。。");
}
}
4.5: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-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> <context:component-scan base-package="com.wkl"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
导入命名空间的参考:https://blog.csdn.net/weixin_42382121/article/details/82050091
4.6:结果
注:寻找这个bean时,寻找的是接口的class
5:当被代理类没有实现接口时,spring自动默认使用CGLIB代理、
@Test
public void test01(){
// UserInter bean = ioc.getBean(UserInter.class);
// bean.add();
// System.out.println(""+bean.getClass());
UserImpl userImpl = (UserImpl) ioc.getBean("userImpl");
userImpl.add();
System.out.println(""+userImpl.getClass());
}
6:AOP中的细节
6.1:当被代理类有实现接口时,默认使用JDK代理;在容器中保存的是代理对象,获取bean只能传接口类型,也可以用id来获取这个接口对象
@Test
public void test01(){
UserInter bean = ioc.getBean(UserInter.class);
bean.add();
System.out.println(""+bean.getClass());
}
@Test
public void test01(){
UserInter bean = ioc.getBean(UserInter.class);
bean.add();
System.out.println(""+bean.getClass());
UserInter userImpl = (UserInter) ioc.getBean("userImpl");
userImpl.add();
System.out.println(""+userImpl.getClass());
}
start。。。。
add.....
logReturn。。。。
logEnd。。。。
class com.sun.proxy.$Proxy18
start。。。。
add.....
logReturn。。。。
logEnd。。。。
class com.sun.proxy.$Proxy18Process finished with exit code 0
6.2:切入点表达式
切入点表达式的语法细节
①切入点表达式的语法格式
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
②举例说明
表达式
execution(* com.atguigu.spring.ArithmeticCalculator.*(..))
含义
ArithmeticCalculator接口中声明的所有方法。
第一个“*”代表任意修饰符及任意返回值。
第二个“*”代表任意方法。
“..”匹配任意数量、任意类型的参数。
若目标类、接口与该切面类在同一个包中可以省略包名。
表达式
execution(public * ArithmeticCalculator.*(..))
含义
ArithmeticCalculator接口的所有公有方法
表达式
execution(public double ArithmeticCalculator.*(..))
含义
ArithmeticCalculator接口中返回double类型数值的方法
表达式
execution(public double ArithmeticCalculator.*(double, ..))
含义
第一个参数为double类型的方法。
“..” 匹配任意数量、任意类型的参数。
表达式
execution(public double ArithmeticCalculator.*(double, double))
含义
参数类型为double,double类型的方法
③在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。
表达式
execution (* *.add(int,..)) || execution(* *.sub(int,..))
含义
任意类中第一个参数为int类型的add方法或sub方法
6.3:AOP执行顺序
try {
@Before():前置通知 在目标方法执行前执行
method.invoke(obj,args);//执行目标方法
@After() :后置通知 在目标方法执行后执行
} catch (Exception e) {
@AfterThrowing() :后置异常通知
}finally {
@AfterReturning() :后置成功通知
}
正常:@Before---@After---@AfterReturning
异常:@Before---@After---@AfterThrowing
6.4:切面类获取目标方法参数和方法名
@Before("execution(public void com.wkl.impl.UserImpl.add())")
//方法开始执行前,写入切入点表达式
//execution(权限访问控制符 返回值类型 方法签名)
public void Start(JoinPoint joinPoint){
//参数信息
Object[] args = joinPoint.getArgs();
//获取方法名--获取签名,然后获取方法名
String name = joinPoint.getSignature().getName();
System.out.println("["+name+"]方法执行了--start。。。。参数是:"+ Arrays.asList(args));
}
[add]方法执行了--start。。。。参数是:[]
add.....
logReturn。。。。
logEnd。。。。
6.5:切面类接受目标方法返回值和异常信息
@AfterThrowing(value = "execution(public String com.wkl.impl.UserImpl.add())",throwing = "e")
//想在目标方法出现异常的时候执行
public void logException(Exceptione){
System.out.println("logException。。。。");
}
//告诉切面类,标识用result接受返回值
@AfterReturning(value = "execution(public String com.wkl.impl.UserImpl.add())",returning = "result")
//想在目标方法结束的时候执行
public void logEnd(Object result){
System.out.println("logEnd。。。。返回值是:"+result);
}
6.6:抽取可重用的切入点表达式
/* * 1:随便定义一个没有实现的返回void的空方法 * 2:提取表达式,并注解 * 3:将方法名注在之前切入点表达式的地方 * */
package com.wkl.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* Description:
* Date: 2020/6/27 - 上午 12:53
* author: wangkanglu
* version: V1.0
*/
@Component
@Aspect
public class LogUtil {
/*
* 1:随便定义一个没有实现的返回void的空方法
* 2:提取表达式,并注解
* 3:将方法名注在之前切入点表达式的地方
* */
@Pointcut("execution(public String com.wkl.impl.UserImpl.add())")
public void MyPonintcat(){}
@Before("MyPonintcat()")
//方法开始执行前,写入切入点表达式
//execution(权限访问控制符 返回值类型 方法签名)
public void Start(JoinPoint joinPoint){
//参数信息
Object[] args = joinPoint.getArgs();
//获取方法名--获取签名,然后获取方法名
String name = joinPoint.getSignature().getName();
System.out.println("["+name+"]方法执行了--start。。。。参数是:"+ Arrays.asList(args));
}
@After("MyPonintcat()")
//想在目标方法正常执行完成之后执行
public void logReturn(){
System.out.println("logReturn。。。。");
}
@AfterThrowing(value = "MyPonintcat()",throwing = "e")
//想在目标方法出现异常的时候执行
public void logException(Exception e){
System.out.println("logException。。。。");
}
//告诉切面类,标识用result接受返回值
@AfterReturning(value = "MyPonintcat()",returning = "result")
//想在目标方法结束的时候执行
public void logEnd(Object result){
System.out.println("logEnd。。。。返回值是:"+result);
}
}
6.7:环绕通知---四合一接口
//环绕通知
@Around("MyPonintcat()")
public Object myAround(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();
Object proceed =null;
try {
System.out.println("环绕前置");
proceed = pjp.proceed(args);
System.out.println("环绕返回通知");
} catch (Throwable throwable) {
System.out.println("环绕异常");
throwable.printStackTrace();
}finally {
System.out.println("环绕后置");
}
return proceed;
}
环绕前置
add.....
环绕返回通知
环绕后置
- 环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
- 对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
- 在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
环绕和普通都在的话,执行顺序:
环绕前置---普通前置---目标方法执行---环绕正常/出现异常---环绕后置---普通后置---普通返回/出现异常
6.8:多切面的执行顺序
至于一切面或者而且二切面的顺序:
1:默认是比较全类名的:
2:可以使用@Order()来指定切面的顺序;在()中填入整数,数越小,优先级越高;
7:基于注解的AOP(可以不改变源码)
<!--基于注解的AOP的步骤
1:将目标类和切面类加入容器中
2:告诉spring那个是切面类
3:在切面类中,那个是方法是合适何地运行
4:开启注解的AOP功能
-->
<!--目标类-->
<bean id="userImpl" class="com.wkl.impl.UserImpl"></bean>
<!--切面类-->
<bean id="logUti" class="com.wkl.utils.LogUtil"></bean>
<!--需要aop命名空间-->
<aop:config>
<!--指定切面类-->
<aop:aspect ref="logUti" order="3">
<!--指定切面表达式-->
<aop:pointcut id="mypoint1" expression="execution(public String com.wkl.impl.UserImpl.add())"/>
<aop:before method="Start" pointcut="execution(public String com.wkl.impl.UserImpl.add())"></aop:before>
<aop:after-returning method="logReturn" pointcut-ref="mypoint1" returning="result"></aop:after-returning>
</aop:aspect>
</aop:config>