Spring系列四:AOP切面编程 第二部分
Spring AOP
- 💗Spring AOP
- 🍝基本介绍
- 🍝AOP编程 快速入门
- 🍝注意事项和细节
- 🍝课后作业
- 🐋AOP-切入表达式
- 💧通配符
- 💧注意事项和细节
💗Spring AOP
🍝基本介绍
●什么是AOP
AOP的全称(aspect oriented programming), 面向切面编程.
●AOP实现方式
1.基于动态代理的方式 [内置aop实现]
2.使用框架aspectj来实现
🍝AOP编程 快速入门
●说明
1.需要引入核心的aspect包
2.在切面类中声明通知方法
1)前置通知: @Before
2)返回通知: @AfterReturning
3)异常通知: @AfterThrowing
4)后置通知: @After 最终通知
5)环绕通知: @Around
●需求说明
我们使用aop编程的方式, 来实现手写的动态代理案例效果, 以上一个案例为例进行讲解.
1.导入AOP编程需要的包
2.新建com.zzw.spring.aop.aspectj.SmartAnimalAble
public interface SmartAnimalAble {//求和float getSum(float i, float j);//求差float getSub(float i, float j);
}
3.新建com.zzw.spring.aop.aspectj.SmartDog
易错点: 不要引入别的包下的SmartAnimalAble, 要引入同包下的SmartAnimalAble
@Component //使用@Component 当spring容器启动时, 将 SmartDog 注入到容器
public class SmartDog implements SmartAnimalAble {@Overridepublic float getSum(float i, float j) {float result = i + j;System.out.println("方法内部打印result = " + result);return result;}@Overridepublic float getSub(float i, float j) {float result = i - j;System.out.println("方法内部打印result = " + result);return result;}
}
4.新建com.zzw.spring.aop.aspectj.SmartAnimalAspect
切面类, 类似于我们前面自己写的MyProxyProvider, 但是功能强大很多
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定)]
@Component //会将SmartAnimalAspect注入到容器
public class SmartAnimalAspect {//希望将showBeginLog方法切入到SmartDog-getSum前执行-前置通知/*** 解读* 1. @Before 表示是前置通知, 即在我们的目标对象执行方法前执行* 2. value = "execution(public float com.zzw.spring.aop.aspectj.SmartDog.getSum(float, float))"* 指定切入到哪个类的哪个方法 形式是: 访问修饰符 返回类型 全类名.方法名(形参列表)* 3. showBeginLog方法可以理解成就是一个切入方法, 这个方法名是可以由程序员指定的 比如:showBeginLog* 4. JoinPoint joinPoint 在底层执行时, 由AspectJ切面编程框架, 会给该切入方法传入 joinPoint对象* , 通过该方法, 程序员可以获取到 相关信息* @param joinPoint*/@Before(value = "execution(public float com.zzw.spring.aop.aspectj.SmartDog.getSum(float, float))")public void showBeginLog(JoinPoint joinPoint) {//通过连接点对象joinPoint, 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + "-参数 "+ Arrays.asList(joinPoint.getArgs()));}//返回通知: 即把showSuccessEndLog方法切入到目标对象方法正常执行完毕后的地方@AfterReturning(value = "execution(public float com.zzw.spring.aop.aspectj.SmartDog.getSum(float, float))")public void showSuccessEndLog(JoinPoint joinPoint) {Signature signature = joinPoint.getSignature();System.out.println("切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName());}//异常通知: 即把showExceptionLog方法切入到目标对象方法执行发生异常后的catch{}@AfterThrowing(value = "execution(public float com.zzw.spring.aop.aspectj.SmartDog.getSum(float, float))")public void showExceptionLog(JoinPoint joinPoint) {Signature signature = joinPoint.getSignature();System.out.println("切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName());}//最终通知: 即把showFinallyEndLog方法切入到目标方法执行后, 不管是否发生异常都要执行, finally{}@After(value = "execution(public float com.zzw.spring.aop.aspectj.SmartDog.getSum(float, float))")public void showFinallyEndLog(JoinPoint joinPoint) {Signature signature = joinPoint.getSignature();System.out.println("切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName());}
}
5.新建src/beans08.xml
具体来说,当你通过 getBean() 方法获取 SmartDog 类时,返回的对象实际上是 Spring 自动生成的代理对象,而不是 SmartDog 类的原始对象。这个代理对象会在目标方法执行前后,以及发生异常时,执行切面类中定义的各种通知方法,从而实现了 AOP 的功能。切入到哪个类, 哪个类就会被代理
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.zzw.spring.aop.aspectj"/><!--开启基于注解的AOP功能--><aop:aspectj-autoproxy/>
</beans>
6.测试com.zzw.spring.aop.aspectj.AopAspectjTest
public class AopAspectjTest {@Testpublic void smartDogTestByProxy() {ApplicationContext ioc =new ClassPathXmlApplicationContext("beans08.xml");//这里我们需要通过接口类型来获取到注入的SmartDog对象-就是代理对象SmartAnimalAble smartAnimalAble =ioc.getBean(SmartAnimalAble.class);smartAnimalAble.getSum(1, 2);//System.out.println("smartAnimalAble运行类型是 " + smartAnimalAble.getClass());//class com.sun.proxy.$Proxy16}
}
🍝注意事项和细节
1.关于切面类方法命名可以自己规范一下, 比如showBeginLog(), showSuccessEndLog(), showExceptionLog(), showFinallyEndLog()
2.切入表达式的更多配置, 比如使用模糊配置
@Before(value=“execution(* com.zzw.aop.proxy.SmartDog.*(…))”)
第一个\*表示:
任意修饰符和返回类型
第二个\*表示:
任意方法名
..表示:
任意形参列表
3.表示所有访问权限, 所有包下的所有类的所有方法, 都会被执行该前置通知方法
@Before(value=“execution(* *.*(…))”)
4.当spring容器开启了 基于注解的AOP功能 <aop:aspectj-autoproxy/>
, 我们获取注入的对象, 需要以接口的类型
来获取, 因为你注入的对象.getClass() 已经是代理类型了
5.当spring容器开启了 基于注解的AOP功能 <aop:aspectj-autoproxy/>
, 我们获取注入的对象, 也可以通过id来获取, 但是也要转成接口类型.
//这里我们需要通过接口类型来获取到注入的SmartDog对象-就是代理对象
SmartAnimalAble smartAnimalAble = ioc.getBean(SmartAnimalAble.class);//SmartAnimalAble smartAnimalAble = (SmartAnimalAble) ioc.getBean("smartDog");
🍝课后作业
●作业要求
1.有接口 UsbInterface (方法 work)
2.实现子类 Phone 和 Camera 实现 UsbInterface
3.请在SmartAnimalAspect 切面类, 写一个方法(可输出日志信息作为前置通知, 在Phone和Camera对象执行work方法前调用
4.其它如返回通知, 异常通知, 后置通知, 也可以加入.
●代码实现
1.新建com.zzw.spring.aop.homework.UsbInterface
接口
public interface UsbInterface {void work(String name);
}
2.新建com.zzw.spring.aop.homework.Phone
@Component //将Phone当作一个组件注入到容器中
public class Phone implements UsbInterface {@Overridepublic void work(String name) {System.out.println(name + " 手机正在工作中....");}
}
3.新建com.zzw.spring.aop.homework.Camera
@Component //将Camera对象注入到Spring容器
public class Camera implements UsbInterface {@Overridepublic void work(String name) {System.out.println(name + " 相机正在工作中....");}
}
4.切面类com.zzw.spring.aop.homework.Phone.SmartAnimalAspect
@Aspect
@Component
public class SmartAnimalAspect {//希望将showBeginLog切入到Phone/Camera-work() 前执行//前置通知//切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效//比如下面我们是对UsbInterface切入, 那么对实现类 Phone/Camera 都生效//@Before(value = "execution(public void *.work(String))")@Before(value = "execution(public void com.zzw.spring.aop.homework.UsbInterface.work(String))")public void showBeginLog(JoinPoint joinPoint) {//通过连接点对象, 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + "-参数"+ Arrays.asList(joinPoint.getArgs()));}//返回通知@AfterReturning(value = "execution(public void UsbInterface.work(String))")//@AfterReturning(value = "execution(public void com.zzw.spring.aop.homework.*.work(String))")public void showSuccessEndLog(JoinPoint joinPoint) {//通过连接点对象, 可以获取方法名Signature signature = joinPoint.getSignature();System.out.println("切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName());}//异常通知//@AfterThrowing(value = "execution(public void *.work(String))")@AfterThrowing(value = "execution(public void UsbInterface.work(String))")public void showExceptionLog(JoinPoint joinPoint) {//通过连接点对象, 可以获取方法名Signature signature = joinPoint.getSignature();System.out.println("切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName());}//后置通知//@After(value = "execution(public void *.work(String))")@After(value = "execution(public void UsbInterface.work(String))")public void showFinallyEndLog(JoinPoint joinPoint) {//通过连接点对象, 可以获取方法名Signature signature = joinPoint.getSignature();System.out.println("切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName());}
}
5.src/beans09.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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><context:component-scan base-package="com.zzw.spring.aop.homework"/><!--开启基于注解的AOP功能--><aop:aspectj-autoproxy/>
</beans>
6.测试com.zzw.spring.aop.homework.AspAspectjTest
public class AspAspectjTest {public static void main(String[] args) {ApplicationContext ioc =new ClassPathXmlApplicationContext("beans09.xml");UsbInterface phone = (UsbInterface) ioc.getBean("phone");phone.work("华为");System.out.println("===================================");UsbInterface camera = (UsbInterface) ioc.getBean("camera");camera.work("索尼");//System.out.println("phone的运行类型是" + phone.getClass());}
}
🐋AOP-切入表达式
💧通配符
切入点表达式
1.作用
通过表达式的方式
定位一个或多个
具体的连接点
2.语法细节
①切入点表达式的语法格式
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名] (参数列表))
②举例说明
表达式 | execution(* com.zzw.spring.aop.aspectj.SmartDog.*(..)) |
---|---|
含义 | 如果SmartAnimalAble是接口, 则表示 接口/类 中声明的所有方法. 第一个 * 代表任意修饰符及任意返回值 第二个 * 表示任意方法 .. 匹配任意数量, 任意类型的参数 (规定.的数量是2个) 若目标类/接口与该切面类在同一个包中可以省略包名 |
表达式 | execution(public * SmartDog.*(..)) |
---|---|
含义 | SmartDog 接口/类 中的所有公有方法 |
表达式 | execution(public double SmartDog.*(..)) |
---|---|
含义 | SmartDog 接口/类 中返回double类型数值的方法 |
表达式 | execution(public double SmartDog.*(double, ..)) |
---|---|
含义 | 第一个参数为double类型的方法. ..匹配任意数量, 任意类型的参数 |
表达式 | execution(public double SmartDog.*(double, double)) |
---|---|
含义 | 参数类型为double, double类型的方法 |
:匹配 com.zzw.bean 包及其子包下的所有类。
表达式 | execution(public double com.zzw.bean..set*( )) |
---|---|
含义 | 匹配 com.zzw.bean 包及其子包下的所有类;set*:匹配所有以 set 开头的方法名 |
③在AspectJ中, 切入点表达式可以通过&&, ||, ! 等操作符结合起来
表达式 | execution(* *.add(int, ..)) || execution(* *.sub(int, ..)) |
---|---|
含义 | 任意类中第一个参数为int类型的add方法或sub方法 |
💧注意事项和细节
1.切入表达式也可以指向类的方法
, 这时切入表达式会对该类/对象生效
2.切入表达式也可以指向接口的方法
, 这时切入表达式会对实现了接口的类/对象生效
3.切入表达式也可以对没有实现接口的类
, 进行切入
●代码实现
1.新建com.zzw.spring.aop.aspectj.Car
@Component //把Car视为一个组件[对象], 注入到Spring容器
public class Car {public void run() {System.out.println("小汽车 run...");}
}
2.com.zzw.spring.aop.aspectj.SmartAnimalAspect
指向类的方法
//切面类
@Aspect //表示是一个切面类
@Component //会将SmartAnimalAspect注入到容器
public class SmartAnimalAspect {//给Car配置一个前置通知@Before(value = "execution(public void Car.run())")public void ok1(JoinPoint joinPoint) {Signature signature = joinPoint.getSignature();System.out.println("切面类的ok1()-执行的目标方法-" + signature.getName());}
}
3.测试com.zzw.spring.aop.homework.AspAspectjTest
public class AopAspectjTest {@Testpublic void test() {ApplicationContext ioc =new ClassPathXmlApplicationContext("beans08.xml");Car car = ioc.getBean(Car.class);//说明: car对象仍然是代理对象System.out.println("car的运行类型=" + car.getClass());//car的运行类型=class com.zzw.spring.aop.aspectj.Car$$EnhancerBySpringCGLIB$$5e9a8b7acar.run();}
}
4.补充: 动态代理jdk的Proxy和Spring的CGlib
具体来说,Spring AOP 使用 JDK 动态代理和 CGLIB 两种方式来生成代理对象,其中选择使用哪种方式取决于被代理的类是否实现了接口。
如果被代理的类实现了接口,Spring AOP 会使用 JDK 动态代理。JDK 动态代理要求被代理的类必须实现至少一个接口,代理对象会实现这个接口并在运行时生成代理实例。
如果被代理的类没有实现接口,Spring AOP 会使用 CGLIB 来生成代理对象。CGLIB 可以代理没有实现接口的类,它通过继承被代理类来生成代理对象。