在学习Spring的AOP之前我们需要补充下设计模式中的代理模式。这块是理解AOP的必备基础内容。
若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静态代理中的代理类和目标类会实现同一接口或是派生自相同的父类。
先定义公共接口
/**
* 代理模式
* 定义的公共接口
*/
public interface SomeService {
String doSome();
}
然后定义目标对象:
/**
* 代理模式
* 目标对象:Target Object
*/
public class SomeServiceImpl implements SomeService{
@Override
public String doSome() {
System.out.println("目标对象执行了。。。");
return "Hello";
}
}
然后定义我们的代理对象:
/**
* 代理模式
* 代理类:需要和目标对象实现相同的接口
*/
public class SomeProxy implements SomeService{
// 代理对象持有的目标对象
private SomeService target;
public SomeProxy(SomeService target){
this.target = target;
}
/**
* 代理对象需要增强的方法
* @return
*/
@Override
public String doSome() {
System.out.println("目标对象执行之前");
// 应该需要让目标对象来完成核心的业务
String msg = target.doSome();
System.out.println("目标对象执行之后");
return msg.toUpperCase();
}
}
最后做测试:
/**
* 静态代理的测试
*/
@Test
public void test1(){
// 获取目标对象
SomeService target = new SomeServiceImpl();
// 获取代理对象
SomeService proxy = new SomeProxy(target);
// 通过代理对象执行方法
System.out.println(proxy.doSome());
}
测试结果:
目标对象执行之前
目标对象执行了。。。
目标对象执行之后
HELLO
代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。 代理类型 使用场景:
如何目标对象实现了相关的接口。那么我们就可以通过JDK动态代理来完成代理类的动态生成。
// 调用目标对象的方法
//String msg = target.doSome();
Object res = method.invoke(target, args); /**
* 实现JDK动态代理:目标对象必须实现相关的接口
* 我们就不需要显示的定义代理类
*/
@Test
public void test2(){
// 1.获取目标对象
SomeService target = new SomeServiceImpl();
// 2.获取代理对象
SomeService proxy = (SomeService) Proxy.newProxyInstance(
Test02.class.getClassLoader(), // 获取类加载器
target.getClass().getInterfaces(), // 获取目标对象实现的所有的接口
new InvocationHandler() { // 提供一个 InvocationHandler的对象
/**
* 该方法是代理对象执行目标对象方法的回调方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
System.out.println(args);
System.out.println("----start------");
// 调用目标对象的方法
String msg = target.doSome();
System.out.println("----end------");
return msg.toUpperCase();
}
}
);
// 3.通过代理对象来执行
System.out.println("proxy.doSome() = " + proxy.doSome());
}
}
执行结果:
如果目标对象没有实现任何的接口。那么我们只能通过CGLIB代理的方式来实现了。同时我们需要单独的添加CGLIB的依赖。
<!-- https://mvnrepository.com/artifact/cglib/cglib-nodep -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
</dependency>
定义目标对象:注意不实现任何接口
/**
* 目标对象的定义
* 改目标对象没有实现任何的接口
*/
public class SomeService {
public String doSome(){
System.out.println("目标对象执行了....");
return "Hello Cglib";
}
}
然后定义cglib的代理对象
/**
* Cglib的代理类
*/
public class CglibProxy implements MethodInterceptor {
// 目标对象
private SomeService target;
public CglibProxy(SomeService target){
this.target = target;
}
/**
* 对外提供代理对象的方法
* @return
*/
public SomeService createTarget(){
// 创建cglib的增强器
Enhancer enhancer = new Enhancer();
// 需要指定父类
enhancer.setSuperclass(SomeService.class);
// 代理后的回调对象
enhancer.setCallback(this);
return (SomeService) enhancer.create();
}
/**
* 这个就是对应的增强的方法
* @param o
* @param method
* @param objects
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("------start-----");
//String msg = target.doSome();
Object res = method.invoke(target, objects);
System.out.println("------end-----");
return msg.toUpperCase();
}
}
然后测试:
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程OOP的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 日志、事务、安全检查等
在学习AOP中我们会涉及到如下的相关概念
术语 | 说明 |
---|---|
切面 | 切面泛指交叉业务逻辑。比如事务处理、日志处理就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强 |
织入 | 织入是指将切面代码插入到目标对象的过程。 |
连接点 | 连接点指切面可以织入的位置。 |
切入点 | 切入点指切面具体织入的位置。 |
通知(Advice) | 通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。 |
顾问(Advisor) | 顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。 不仅指定了切入时间点,还可以指定具体的切入点 |
下面这个图会更加的形象些:
通知的类型:
通知类型 | 说明 |
---|---|
前置通知(MethodBeforeAdvice) | 目标方法执行之前调用 |
后置通知(AfterReturningAdvice) | 目标方法执行完成之后调用 |
环绕通知(MethodInterceptor) | 目标方法执行前后都会调用方法,且能增强结果 |
异常处理通知(ThrowsAdvice) | 目标方法出现异常调用 |
最终通知(final Advice) | 无论程序执行是否正常,该通知都会执行。类似于try…catch中finally代码块 |
对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。在Spring中使用AOP开发时,一般使用AspectJ的实现方式.
相关说明:
首先定义对应的接口
public interface Calculator {
int add(int i, int j);
int sub(int i ,int j);
int mul(int i , int j);
int div(int i , int j);
}
然后创建该接口的实现
package com.boge.service.impl;
import com.boge.service.Calculator;
import org.springframework.stereotype.Component;
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
创建对应的切面类
/**
* 切面类
*/
@Aspect // 被该注解所修饰的Java类就是一个切面类
@Component
public class LogAspect {
/**
* 前置通知:@Before()
*/
@Before("execution(public int com.boge.service.impl.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("前置通知执行了。。。。");
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
}
}
然后做对应的测试
@Test
public void test1(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Calculator bean = ac.getBean(Calculator.class);
System.out.println(bean.add(4, 7));
System.out.println(bean.sub(4, 7));
System.out.println(bean.mul(4, 7));
}
结果打印:
相关的通知的案例:
@Aspect // 被该注解所修饰的Java类就是一个切面类
@Component
public class LogAspect {
/**
* 前置通知:@Before()
*/
@Before("execution(public int com.boge.service.impl.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("前置通知执行了。。。。");
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
}
/**
* 后置通知:可以获取目标方法的返回结果
*/
@AfterReturning(value = "execution(* com.boge.service.impl.*.*(..))",returning = "res")
public void afterReturningMethod(JoinPoint joinPoint,Object res){
String methodName = joinPoint.getSignature().getName();
System.out.println("后置通知:" + methodName + " 返回结果:" + res);
}
/**
* 环绕通知
*/
@Around("execution(* com.boge.service.impl.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
Object obj = null;
try {
System.out.println("环绕通知执行之前....");
obj =joinPoint.proceed(); // 执行目标对象的方法
System.out.println("环绕通知执行之后....");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知执行异常....");
}finally {
System.out.println("环绕通知执行....最终完成");
}
return obj;
}
/**
* 异常通知
*/
@AfterThrowing(value = "execution(* com.boge.service.impl.*.*(..))",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("异常通知:" + methodName + " " + ex);
}
/**
* 最终通知
*/
@After(value = "execution(* com.boge.service.impl.*.*(..))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("最终通知执行了..." + methodName);
}
}
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号
语法要求:
作用:
细节介绍:
如果一个切入点表达式需要被重复的复用。那么我们可以通过@Pointcut注解来定义表达式。然后我们在通知调用即可:
package com.boge.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 切面类
*/
@Aspect // 被该注解所修饰的Java类就是一个切面类
@Component
public class LogAspect2 {
/**
* 定义一个切入点表达式
*/
@Pointcut("execution(public int com.boge.service.impl.CalculatorImpl.*(..))")
public void ponitCut(){
}
/**
* 前置通知:@Before()
*/
@Before("ponitCut()")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("前置通知执行了。。。。");
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
}
/**
* 后置通知:可以获取目标方法的返回结果
*/
@AfterReturning(value = "ponitCut()",returning = "res")
public void afterReturningMethod(JoinPoint joinPoint,Object res){
String methodName = joinPoint.getSignature().getName();
System.out.println("后置通知:" + methodName + " 返回结果:" + res);
}
/**
* 环绕通知
*/
@Around("ponitCut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
Object obj = null;
try {
System.out.println("环绕通知执行之前....");
obj =joinPoint.proceed(); // 执行目标对象的方法
System.out.println("环绕通知执行之后....");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知执行异常....");
}finally {
System.out.println("环绕通知执行....最终完成");
}
return obj;
}
/**
* 异常通知
*/
@AfterThrowing(value = "ponitCut()",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("异常通知:" + methodName + " " + ex);
}
/**
* 最终通知
*/
@After(value = "ponitCut()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("最终通知执行了22..." + methodName);
}
}
在Spring中AOP还有基于XML的实现方式。当然这种不是我们常用的方案。但是我们还是需要了解下
先定义对应的切面类:
/**
* 切面类
*/
@Component
public class LogAspect3 {
/**
* 前置通知:@Before()
*/
public void beforeMethod(JoinPoint joinPoint){
System.out.println("前置通知执行了。。。。");
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
}
/**
* 后置通知:可以获取目标方法的返回结果
*/
public void afterReturningMethod(JoinPoint joinPoint,Object res){
String methodName = joinPoint.getSignature().getName();
System.out.println("后置通知:" + methodName + " 返回结果:" + res);
}
/**
* 环绕通知
*/
public Object aroundMethod(ProceedingJoinPoint joinPoint){
Object obj = null;
try {
System.out.println("环绕通知执行之前....");
obj =joinPoint.proceed(); // 执行目标对象的方法
System.out.println("环绕通知执行之后....");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知执行异常....");
}finally {
System.out.println("环绕通知执行....最终完成");
}
return obj;
}
/**
* 异常通知
*/
public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("异常通知:" + methodName + " " + ex);
}
/**
* 最终通知
*/
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("最终通知执行了..." + methodName);
}
}
然后定义对应的配置文件
<?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">
<!-- 添加扫描路径 -->
<context:component-scan base-package="com.boge.*"></context:component-scan>
<!-- 基于XML的AOP实现 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="logAspect3">
<!-- 定义切入点表达式 -->
<aop:pointcut id="pointCut" expression="execution(* com.boge.service.impl.*.*(..))"/>
<!-- 配置相关的通知 -->
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointCut" returning="res"></aop:after-returning>
<aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCut" throwing="ex"></aop:after-throwing>
<aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
</aop:aspect>
</aop:config>
</beans>
然后测试即可
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有