SSM03-Spring Aop(xml配置/注解开发)

四、Spring AOP面向切面编程

4.1 Spring AOP概述

AOP是Spring 的重要特性,AOP是通过预编译方式和运行期间动态代理实现程序功能,也就是说可以在不修改源代码的情况下,给程序统一添加功能。

AOP定义:

AOP即面向切面编程。AOP主张将程序中相同的业务逻辑进行横向隔离,并将重复的业务逻辑抽取到一个独立的模块中,以达到提高程序可重用性和开发效率的目的。

AOP中常用术语:

  • 切面:是指关注点(指类中重复的代码)形成的类,通常是指封装的、用于横向插入系统的功能类。在实际开发中,该类被Spring容器识别为切面,需要在配置文件中通过<bean>元素指定。

  • **连接点:**程序执行过程中某个特定的节点,例如,某方法调用时或处理异常时。在Spring AOP中,一个连接点通常是一个方法的执行。

  • 切入点: 当某个连接点满足预先指定的条件时,AOP就能够定位到这个连接点,在连接点处插入切面,该连接点也就变成了切入点。

  • **通知/增强处理:**就是插入的切面程序代码。可以将通知/增强处理理解为切面中的方法

  • **目标对象:**是指被插入切面的方法

  • **织入:**将切面代码插入到目标对象上,从而生成代理对象的过程。

  • **代理:**将通知应用到目标对象之后,程序动态创建的通知对象,就称为代理。

  • **引介:**是一种特殊的通知,它可为目标对象添加一些属性和方法。这样,即使一个业务类原本没有实现某一个接口,通过AOP的引介功能,也可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

image-20241217215539617

4.2 Spring AOP的实现机制

Spring AOP实现时需要创建一个代理对象,根据代理对象的创建方式,可以将AOP实现机制分为两种:一种是JDK动态代理;一种是CGLib动态代理。

动态代理是指在程序运行时创建一个代理对象,而不是在编译时生成的静态代理。代理对象可以代替目标对象,并控制对目标对象方法的调用。

4.2.1 JDK动态代理

默认情况下,Spring AOP使用JDK动态代理,JDK动态代理是通过java.lang.reflect.Proxy 类实现的,可以调用Proxy类的**newProxyInstance()**方法创建代理对象。JDK动态代理可以实现无侵入式的代码扩展,并且可以在不修改源代码的情况下,增强某些方法。

  • **要求:**代理的目标对象必须实现某些接口
  • **实现:**为UserDao中的方法通过JDK动态代理添加权限检查和日志方法
public interface UserDao {
    public void addUser();
    public void deleteUser();
}

public class UserDaoImpl implements UserDao {
	public void addUser() {
		System.out.println("添加用户");	}
	public void deleteUser() {
		System.out.println("删除用户");	}
}
// 切面类:存在多个通知Advice(增强的方法)
public class MyAspect {
    public void check_Permissions(){
        System.out.println("模拟检查权限...");		
	}
    public void log(){
        System.out.println("模拟记录日志...");		
	}
}

创建代理类MyProxy,该类需要实现InvocationHandler接口设置代理类的调用处理程序。在代理类中,通过newProxyInstance()生成代理方法。

public class MyProxy implements InvocationHandler {
    //声明目标接口
    private UserDao userDao;

    /**
    创建代理对象:
    Proxy.newProxyInstance(classLoader, interfaces, this)
    ClassLoader loader:类加载器,将此代理对象加载到JDK中
    Class<?>[] interfaces:目标对象所实现的所有接口
    InvocationHandler h:方法拦截,所有动态代理类的方法调用,都会交由h.invoke()方法去处理                             
    */
    public Object createProxy(UserDao userDao) {
        this.userDao = userDao;
        //获取类加载器
        ClassLoader classLoader = MyProxy.class.getClassLoader();
        //获取目标对象的所有接口
        Class<?>[] interfaces = userDao.getClass().getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println(anInterface);
        }
       return Proxy.newProxyInstance(classLoader, interfaces, this);
    }


    /**
    所有动态代理类的方法调用,都会交由invoke()方法去处理    
    proxy:代理对象
    method:将要被执行的方法详细(反射)原先目标对象的方法
    args:执行方法所需的参数
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //创建切面对象
        MyAspect myAspect = new MyAspect();
        //前增强
        myAspect.permission();
        //目标对象上调用方法
        Object invoke = method.invoke(userDao, args);
        //后增强
        myAspect.log();
        return invoke;
    }
}
public class JDKTest {
    public static void main(String[] args) {
        MyProxy myProxy = new MyProxy();
        UserDao userDao = new UserDaoImpl();
        UserDao proxy = (UserDao) myProxy.createProxy(userDao);

        proxy.addUser();
        System.out.println();
        proxy.deleteUser();
    }
}
4.2.2 CGLib动态代理

JDK动态代理存在缺陷,它只能为接口创建代理对象,当需要为类创建代理对象时,就需要使用CGLib(Code Generation Library)动态代理,CGLib动态代理不要求目标类实现接口,它采用底层的字节码技术,通过继承的方式动态创建代理对象。

public class CglibProxy implements MethodInterceptor {

    //代理方法
    public Object createProxy(Object target) {
        //创建一个动态对象类
        Enhancer enhancer = new Enhancer();
        //确定需要增强的类设为父类
        enhancer.setSuperclass(target.getClass());
        //添加回调函数
        enhancer.setCallback(this);
        //返回创建的代理类
        return enhancer.create();
    }


    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //创建切面对象
        MyAspect myAspect = new MyAspect();
        //前增强
        myAspect.permission();
        //目标对象上调用方法
        Object invoke = methodProxy.invokeSuper(proxy, args);
        //后增强
        myAspect.log();
        return invoke;
    }
}

4.3 基于XML的AOP实现

因为Spring AOP中的代理对象由IoC容器自动生成,所以开发者无须过多关注代理对象生成的过程,只需选择连接点、创建切面、定义切点并在XML文件中添加配置信息即可。

元素描述
< aop:config>Spring AOP配置的根元素
  • 配置切面
元素描述
<aop:aspect>配置切面
属性名称描述
id用于定义该切面的唯一标识
ref用于引用普通的Spring Bean
<bean id="id" class="">
<aop:aspect id="" ref="id">

在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,因此,在使用<aop:aspect>元素之前,要在配置文件中先定义一个普通的Bean。

  • 配置切入点

当切入点作为 <aop:config>子元素时,切入点可被多个切面共享

当切入点作为<aop:aspect> 子元素时,为局部切入点

元素描述
<aop:pointcut>配置切入点
属性名称描述
id用于指定切入点的唯一标识
expression用于指定切入点关联的切入点表达式
//切入点表达式
//execution(修饰符?返回值类型  类路径?方法名(参数)异常类型?)
execution(* com.itheima.jdk.*.*(..)) 
*:返回值为任意类型
com.tyut.jdk.*:拦截com.tyut.jdk包下的所有类(目标方法)
*(..):所有方法,任意参数
  • 配置通知
元素描述
<aop:before>配置前置通知,在目标方法执行前实施增强,可以应用于权限管理等功能
<aop:after>配置后置通知,在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能
<aop:around> 配置环绕通知,在目标方法执行前后实施增强,可以应用于日志、事务管理等功能
<aop:after-returning> 配置返回通知,在目标方法成功执行之后调用通知
<aop:after-throwing> 配置异常通知,在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能
属性说明
pointcut该属性用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入该通知。
pointcut-ref该属性指定一个已经存在的切入点名称。
method该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理。
throwing该属性只对<after-throwing>元素有效,它用于指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常。
returning该属性只对<after-returning>元素有效,它用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值。
  • 相关依赖
<!--  aspectjrt包的依赖   
aspectjrt 提供了 AspectJ 所需的核心运行时支持。
aspectjweaver 负责实际的编织过程,确保切面被应用到目标类
-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.1</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
  • 目标类
public interface UserDao {
    public void addUser();
    public void deleteUser();
}

public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("添加用户");
        int x = 1 / 0;
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}
  • 切面类
public class XmlAdvice {
    //前置通知
    public void before(JoinPoint joinPoint) {
        System.out.println("这是前置通知");
        System.out.println("目标类" + joinPoint.getTarget());
        System.out.println("目标方法为" + joinPoint.getSignature().getName());
    }
    //返回通知
    public void afterReturning(JoinPoint joinPoint) {
        System.out.println("返回通知方法(未异常)");
        System.out.println("目标方法为" + joinPoint.getSignature().getName());
    }
    //环绕通知
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("这是环绕通知前半部分");
        //调用目标方法
        Object proceed = point.proceed();
        System.out.println("这是环绕通知后半部分");
        return proceed;
    }
    //异常通知
    public void afterException() {
        System.out.println("异常通知");
    }
    //后置通知
    public void after() {
        System.out.println("这是后置通知");
    }
}
  • 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: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/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 注册Bean   -->
    <bean name="userDao" class="com.tyut.example07.UserDaoImpl"/>
    <bean name="xmlAdvice" class="com.tyut.example07.XmlAdvice"/>

    <!-- 配置Spring AOP -->
    <aop:config>
        <!-- 设置切入点  -->
        <aop:pointcut id="pointcut" expression="execution(* com.tyut.example07.UserDaoImpl.*(..))"/>

        <!--  设置切面  -->
        <aop:aspect id="xmlAspect" ref="xmlAdvice">
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
            <aop:around method="around" pointcut-ref="pointcut"/>
            <aop:after-throwing method="afterException" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

4.4 基于注解的AOP实现

元素描述
@Aspect配置切面
@Pointcut配置切点
@Before配置前置通知
@After配置后置通知
@Around配置环绕方式
@AfterReturning配置返回通知
@AfterThrowing配置异常通知
@Aspect
public class XmlAdvice {
    //配置切点
    @Pointcut("execution(* com.tyut.example08.UserDao.*(..))")
    public void pointcut() {

    }

    //前置通知
    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("这是前置通知");
        System.out.println("目标类" + joinPoint.getTarget());
        System.out.println("目标方法为" + joinPoint.getSignature().getName());
    }
    //返回通知
    @After("pointcut()")
    public void afterReturning(JoinPoint joinPoint) {
        System.out.println("返回通知方法(未异常)");
        System.out.println("目标方法为" + joinPoint.getSignature().getName());
    }
    //环绕通知
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("这是环绕通知前半部分");
        //调用目标方法
        Object proceed = point.proceed();
        System.out.println("这是环绕通知后半部分");
        return proceed;
    }
    //异常通知
    @AfterThrowing("pointcut()")
    public void afterException() {
        System.out.println("异常通知");
    }
    //后置通知
    @After("pointcut()")
    public void after() {
        System.out.println("这是后置通知");
    }
}
<?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: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/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 注册Bean   -->
    <bean name="userDao" class="com.tyut.example08.UserDaoImpl"/>
    <bean name="xmlAdvice" class="com.tyut.example08.XmlAdvice"/>

    <!-- 开启Aop自动代理   -->
    <aop:aspectj-autoproxy/>
</beans>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值