SSM复习总结-Spring AOP

代理设计模式

代理设计模式的优点:将通用性的工作都交给代理对象完成,被代理对象只需关乎自己的核心业务。

静态代理

静态代理,代理类只能够为特定的类生产代理对象,不能代理任意类

在这里插入图片描述
使用代理的好处

  1. 被代理类中只用关注核心业务的实现,将通用的管理型逻辑(事务管理、日志管理)和业务逻辑分离
  2. 将通用的代码放在代理类中实现,提供了代码的复用性
  3. 通过在代理类添加业务逻辑,实现对原有业务逻辑的扩展(增强)

动态代理

动态代理,几乎可以为所有的类产生代理对象
动态代理的实现方式有两种:
JDK动态代理
CGLib动态代理

JDK动态代理

package com.qfedu.dao;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/9 17:36
 * @Description JDK动态代理:是通过被代理对象实现的接口产生其代理对象的
 * 1. 创建一个类,实现InvocationHandler接口,实现invoke方法
 * 2.在类中定义一个Object类型的变量,并提供这个变量的有参构造器,用于将被代理对象传递进来
 * 3.创建getProxy方法,用于创建并返回代理对象
 */
public class JdkDynamicProxy implements InvocationHandler {
    /**
     * 被代理对象
     */
    private Object object;

    public JdkDynamicProxy(Object object) {
        this.object = object;
    }

    /**
     * 产生代理对象,返回代理对象
     */
    public Object getProxy() {
        /**1.获取被代理对象的类加载器*/
        ClassLoader classLoader = object.getClass().getClassLoader();
        /**2.获取被代理对象的类实现的接口*/
        Class<?>[] interfaces = object.getClass().getInterfaces();
        /**3.产生代理对象(通过被代理对象的类加载器及实现的接口)
         * 第一个参数:被代理对象的类加载器
         * 第二个参数:被代理对象实现的接口
         * 第三个参数:使用产生代理对象调用方法时,用于拦截方法执行的处理器
         * */
        Object obj = Proxy.newProxyInstance(classLoader, interfaces, this);
        return obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("----------------拦截");
        begin();
        /**执行method方法(传递进来的方法)*/
        Object obj = method.invoke(object,args);
//        System.out.println(method);
        commit();
        return obj;
    }

    public void begin() {
        System.out.println("-----开启事务");
    }

    public void commit() {
        System.out.println("-----提交事务");
    }
}

测试代码

package com.qfedu.dao;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/9 17:46
 * @Description
 */
public class JdkDynamicProxyTest{
    public static void main(String[] args) {
        /**被代理对象*/
        BookDAOImpl bookDAO=new BookDAOImpl();
        StudentDAOImpl studentDAO=new StudentDAOImpl();
        /**创建动态代理对象,并将被代理对象传递到代理类中赋值给obj*/
        JdkDynamicProxy jdkDynamicProxy=new JdkDynamicProxy(bookDAO);
        /**proxy就是产生的代理对象:产生的代理对象可以强转成被代理对象实现的接口类型*/
        GeneralDAO proxy=(GeneralDAO)jdkDynamicProxy.getProxy();
        /**使用代理对象调用方法,不会直接进入被代理类,而是进入到创建代理对象时指定的InvocationHandler类中的invoke方法,
         *调用的方法作为一个Method参数,传递给了invoke方法
         */
        proxy.delete();
    }
}

测试结果
在这里插入图片描述

CGLib动态代理

由于JDK动态代理是通过被代理类实现的接口来创建代理对象的,因此JDK动态代理只能实现了接口的类的对象。如果一个类没有实现任何接口,该如何产生代理对象呢?
CGLib动态代理,是通过创建被代理类的子类来创建代理对象的,因此即使没有实现任何接口的类也可以通过CGLib产生代理对象
CGLib动态代理不能为final类创建代理对象

  • 添加CGLib的依赖
 <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
package com.qfedu.dao;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/9 20:52
 * @Description
 * 1.添加cglib依赖
 * 2. 创建一个类,实现MethodInterceptor,同时实现接口intercept方法
 * 3. 在类中定义一个Object类型的变量,并提供这个变量的有参构造器,用于传递被代理对象
 * 4. 定义getProxy方法创建并返回代理对象(代理对象是通过创建被代理类的子类来创建的)
 */
public class CGLibDynamicProxy implements MethodInterceptor {
    private Object object;
    public CGLibDynamicProxy(Object object){
        this.object=object;
    }
    public Object getProxy(){
        Enhancer enhancer=new Enhancer();
        /**设置父类*/
        enhancer.setSuperclass(object.getClass());
        /***/
        enhancer.setCallback(this);
        return enhancer.create();
    }
    public void begin() {
        System.out.println("-----开启事务");
    }

    public void commit() {
        System.out.println("-----提交事务");
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib拦截");
        begin();
         /**通过反射调用被代理类的方法*/
        Object obj = method.invoke(object,objects);
        commit();
        return obj;
    }
}

测试

package com.qfedu.dao;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/9 21:09
 * @Description
 */
public class CGLibDynamicProxyTest {
    public static void main(String[] args) {
        /**创建被代理对象*/
        BookDAOImpl bookDAO=new BookDAOImpl();
        /**通过cglib动态代理类创建代理对象*/
        CGLibDynamicProxy cgLibDynamicProxy=new CGLibDynamicProxy(bookDAO);
        /**代理对象实际上是被代理对象子类,因此代理对象可直接强转为被代理类类型*/
        BookDAOImpl obj= (BookDAOImpl) cgLibDynamicProxy.getProxy();
        /**使用对象调用方法,实际上并没有执行这个方法,而是执行了代理类中的intercept犯法,将当前调用的方法以及方法中的参数
         * 传递到intercept方法
         */
        obj.delete();
    }
}

在这里插入图片描述

Spring AOP

AOP概念

Aspect Oriented Programming面向切面编程,是一种利用“横切”的技术(底层实现就是动态代理),对原有的业务逻辑进行拦截,并且可以在这个拦截的横切面上添加特定的业务逻辑,对原有的业务进行增强。
基于动态实现在不改变原有业务的情况下

在这里插入图片描述

框架部署

框架依赖

 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.19.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.19.RELEASE</version>
        </dependency>

创建Spring配置文件

  • 需要引入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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    
</beans>

AOP-基于XML

在DAO的方法添加开启事务和提交事务的逻辑

创建一个类,定义要添加的业务逻辑

public class TxManager {
    public void begin(){
        System.out.println("------开启事务");
    }
    public void commit(){
        System.out.println("-----提交事务");
    }
}

配置aop

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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://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:annotation-config/>
    <context:component-scan base-package="com.qfedu"/>
    <aop:config>
        <!--声明切入点-->
        <aop:pointcut id="bookUpdate" expression="execution(* com.qfedu.dao.*.*(..))"/>
        <!--声明txManager为切面类-->
        <aop:aspect ref="txManager">
            <!--通知-->
            <aop:before method="begin" pointcut-ref="bookUpdate"/>
            <aop:after method="commit" pointcut-ref="bookUpdate"/>
        </aop:aspect>        
    </aop:config>
</beans>
package com.qfedu.utils;

import org.springframework.stereotype.Component;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/9 22:42
 * @Description
 */
@Component
public class TxManager {
    public void begin(){
        System.out.println("------开启事务");
    }
    public void commit(){
        System.out.println("-----提交事务");
    }
}
package com.qfedu.dao;

import org.springframework.stereotype.Component;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/9 16:43
 * @Description
 */
@Component(value = "bookDAO")
public class BookDAOImpl{
    public void insert() {
        System.out.println("insert book");
    }

    public void delete(int id) {
        System.out.println("delete book id = "+id);
    }

    public void update() {
        System.out.println("update book");
    }
}

测试代码

package com.qfedu.test;

import com.qfedu.dao.BookDAOImpl;
import com.qfedu.dao.StudentDAOImpl;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/9 22:33
 * @Description
 */
public class TestCase {
    public static void main(String[] args) {
        /**通过Spring容器获取BookDAOImpl的对象,并调用方法*/
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDAOImpl bookDAO=context.getBean("bookDAO",BookDAOImpl.class);
        bookDAO.insert();
        bookDAO.delete(2);
    }
}

测试结果
在这里插入图片描述
AOP开发步骤

  1. 创建切面类,在切面类定义切点方法
  2. 将切面类配置给Spring容器
  3. 声明切入点
  4. 配置AOP的通知策略

切入点声明

各种切入点声明方式

例如定义切入点表达式execution (* com.qfedu.dao..*. *(..))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个*号:表示返回类型, *号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点..表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个*号:表示类名,*号表示所有的类。
5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数

 <aop:config>
        <!--使用aop:pointcut标签声明切入点:切入点可以是一个方法-->
        <aop:pointcut id="bookUpdate" expression="execution(* com.qfedu.dao.*.*(..))"/>
        <!--BookDAOImpl类中所有无参数无返回值的方法-->
        <aop:pointcut id="book_pc1" expression="execution(void com.qfedu.dao.BookDAOImpl.*())"/>
        <!--BookDAOImpl类中所有无返回值的方法-->
        <aop:pointcut id="book_pc2" expression="execution(void com.qfedu.dao.BookDAOImpl.*(..))"/>
        <!--BookDAOImpl类中所有无参数的方法-->
        <aop:pointcut id="book_p3" expression="execution(* com.qfedu.dao.BookDAOImpl.*())"/>
        <!--BookDAOImpl类中所有方法-->
        <aop:pointcut id="book_p4" expression="execution(* com.qfedu.dao.BookDAOImpl.*(..))"/>
        <!--dao包中所有类中的所有方法-->
        <aop:pointcut id="p5" expression="execution(* com.qfedu.dao.*.*(..))"/>
        <!--dao包中所有类中的insert方法-->
        <aop:pointcut id="p6" expression="execution(* com.qfedu.dao.*.insert(..))"/>
        <!--dao包中所有类中的方法-->
        <aop:pointcut id="p7" expression="execution(* *(..))"/>
        <!--声明txManager为切面类-->
        <aop:aspect ref="txManager">
            <!--通知-->
            <aop:before method="begin" pointcut-ref="p7"/>
            <aop:after method="commit" pointcut-ref="p7"/>
        </aop:aspect>
        <aop:aspect ref="logManager">
<!--            <aop:before method="printLog" pointcut-ref=""/>-->
        </aop:aspect>
    </aop:config>

AOP使用注意事项

 /**通过Spring容器获取BookDAOImpl的对象,并调用方法*/
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDAOImpl bookDAO=context.getBean("bookDAO",BookDAOImpl.class);
        bookDAO.insert();
        bookDAO.delete(2);
        /**如果要使用Spring aop面向切面编程,调用切入点方法的对象必须通过Spring容器获取
         * 如果一个类中的方法被声明为切入点并且织入切点之后,通过Spring容器获取对象,实则获取到的是一个代理对象
         * 如果一个类中的方法没有被声明为切入点,通过Spring容器获取的就是这个类真实创建的对象
         * */
        BookService bookService=context.getBean("bookService",BookService.class);
        bookService.addBook();

AOP通知策略

AOP通知策略:就是声明将切面类中的切点方法如何织入到切入点

  • before
  • after
  • after-throwing
  • after-returning
  • around
    定义切面类
package com.qfedu.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/12 18:04
 * @Description
 */
@Component
public class MyAspect {
    public void method1(){
        System.out.println("method1");
    }
    public void method2(){
        System.out.println("method2");
    }
    public void method3(){
        System.out.println("method3");
    }
    public void method4(){
        System.out.println("method4");
    }
    /**
     *环绕通知的切点方法必须遵守如下规则:
     *  1. 必须带有一个ProceedingJoinPoint类型的参数,
     *  2. 必须有Object类型的返回值
     *  3.在前后增强的业务逻辑之间执行 Object o=joinPoint.proceed();
     *  4. 方法最后返回o
     * */
    public Object method5(ProceedingJoinPoint joinPoint) throws Throwable {
        /**此行代码的执行,表示切入点代码的执行*/
        Object o=joinPoint.proceed();
        System.out.println("环绕通知");
        System.out.println("joinPoint="+joinPoint);
        System.out.println("o="+o);
        System.out.println("method5");
        return o;
    }
}

配置切面类

<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://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:annotation-config/>
    <context:component-scan base-package="com.qfedu"/>
    <aop:config>
        <!--dao包中所有类中的insert方法-->
        <aop:pointcut id="p6" expression="execution(* com.qfedu.dao.*.insert(..))"/>
        <!--声明txManager为切面类-->
        <aop:aspect ref="myAspect">
            <!--aop:before 前置通知,指定切入点之前-->
            <aop:before method="method1" pointcut-ref="p6"/>
            <!--aop:after 后置通知,指定切入点之后-->
            <aop:after method="method2" pointcut-ref="p6"/>
            <!--aop:after-throwing异常通知,切入点方法抛出异常之后-->
            <aop:after-throwing method="method3" pointcut-ref="p6"/>
            <!--after-returning:方法返回值返回之后,对于一个java方法而言,return 返回值
            也是方法的一部分,因此,“方法返回值返回之后”和“方法执行结束”是同一个时间点,所以,
            after和after-returning根据配置的顺序决定执行的顺序
            -->
            <aop:after-returning method="method4" pointcut-ref="p6"/>
            <aop:around method="method5" pointcut-ref="p6"/>
<!--            <aop:around method="method2" pointcut-ref="p6"/>-->
        </aop:aspect>
    </aop:config>
</beans>

测试

package com.qfedu.test;

import com.qfedu.dao.BookDAOImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/12 18:11
 * @Description
 */
public class TestCase2 {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDAOImpl bookDAO=context.getBean("bookDAO",BookDAOImpl.class);
        bookDAO.insert();
    }
}

测试结果
在这里插入图片描述

Spring AOP 注解配置

  1. 创建Maven工厂
  2. 添加Spring依赖(context,aspects)
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.19.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.19.RELEASE</version>
        </dependency>
    </dependencies>
  1. Spring配置文件
<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="com.qfedu"/>
    <!--基于注解配置的AOP代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
  1. AOP注解配置案例
package com.qfedu.dao;

import org.springframework.stereotype.Component;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/12 19:33
 * @Description
 */
@Component(value = "userDAO")
public class UserDAOImpl {
    public void insert(){
        System.out.println("insert-------user");
    }
    public void delete(int id){
        System.out.println("delete id="+id);
    }
}
package com.qfedu.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/12 19:41
 * @Description 切面类
 */
@Component
@Aspect
public class TransactionManager {
    /**
     * 声明切入点
     * p1:切入点名称
     */
    @Pointcut("execution(* com.qfedu.dao.*.*(..))")
    public void p1(){

    }

    /**
     *@Before: 前置通知
     * begin:前置通知名称
     * p1():切入点方法
     */
    @Before("p1()")
    public void begin(){
        System.out.println("----开启事务");
    }

    /**
     * @After: 后置通知
     */
    @After("p1()")
    public void commit(){
        System.out.println("----提交");
    }

    /**
     * 环绕通知
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("p1()")
    public Object printExecuteTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long time1 = System.currentTimeMillis();
        Object o =joinPoint.proceed();
        System.out.println("joinPoint="+joinPoint);
        System.out.println(o);
        long time2=System.currentTimeMillis();
        System.out.println("time="+(time2-time1));
        return o;
    }
}

注意: 注解使用虽然方便,但是只能在源码上添加注解,因此我们的自定义类提倡使用注解配置,如果使用到第三方提供的类则需要通过xml配置形式完成配置。
测试代码

package com.qfedu.test;

import com.qfedu.dao.UserDAOImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author Helen
 * @version 1.0
 * @createTime 2022/1/12 19:34
 * @Description
 */
public class TestCase {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDAOImpl userDAO= context.getBean("userDAO", UserDAOImpl.class);
        userDAO.insert();
        userDAO.delete(2);
    }
}

测试结果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值