Spring——spring的AOP(未使用注解方式和使用注解方式)以及一个综合案例

spring的AOP实现(未使用注解方式)

- 1.引入:
通过上一篇文章,我们已经知道了关于代理设计模式来实现AOP的方式,其实昨天所说的东西在开发中并不会这样去使用,所以今天我们来学习怎么通过spring来实现aop,但是spring的底层也是基于上一篇文章里所说的方式来实现的

- 2.spring AOP中的基本概念(这里就用通俗的话来解释):
连接点:
比如web层调用service层里的方法,这一个过程就叫连接点。
切入规则和切入点:
通过切入规则,筛选出来的连接点,就叫切入点。也就是筛选出哪些方法需要被代理。
切面:
就是一个类,里面有一些特殊的方法用来处理spring拦截下来的切入点,对切入点进行处理。
通知:
spring拦截下来的切入点会交由切入面的特殊方法(通知)来进行处理。
目标对象:
真正被调用完成操作的对象,可以理解为被代理对象,至于它到底是不是被代理对象,就看它符合不符合切入规则,是不是切入点。

关于spring的aop,spring很聪明的给大家准备了自动的代理对象,这个代理对象就不需要我们自己去创建,之前我们的做法是自己写一个代理类,类中再去声明被代理者,而这一次,代理对象已经由spring来完成,我们需要做的,仅仅是筛选切入点,写切入面即可。如下图:
在这里插入图片描述
**

- 3.通过xml配置文件实现Spring AOP

**
a.首先自己写一个切面类
b.在切面类中定义通知
c.在xml文件里配置切面类如下图:
(关于切入点表达式和通知类型,后面做讲解)
在这里插入图片描述

- 4.切入点表达式
a. within()表达式:该表达式是粗粒度的,只能筛选到within(包名.类名),类中的所有连接点(也就是方法),都会变成切入点。
b. execution()表达式:细粒度的切入点表达式,可以以方法为单位定义切入点规则,execution(返回值类型 包名.类名.方法名(参数类型,参数类型…))。
切入点详细的规则这里我不想做阐述了,想看自己去找,有很多的。

- 5.Spring的五大通知类型

前置通知:
在执行目标方法之前执行的通知。前置通知方法,可以没有参数,也可以额外接收一个JoinPoint,Spring会自动将该对象传入,代表当前的连接点,通过该对象可以获取目标对象目标方法相关的信息。
(注意:如果接收JoinPoint,必须保证其为方法的第一个参数,否则报错)
配置文件中通过aop:before来表明该方法是前置通知
在这里插入图片描述
环绕通知:
执行目标方法之前,以及目标方法执行完成之后。需要注意的是,环绕通知中的目标方法需要手动执行,通过ProceedingJoinPoint来实现的,可以在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,这个参数必须处在环绕通知的第一个形参位置。
在这里插入图片描述
在这里插入图片描述
后置通知:
在目标方法执行之后成功执行的通知。在后置通知中也可以选择性的接收一个JoinPoint来获取连接点的额外信息,但是这个参数必须处在参数列表的第一个。
在这里插入图片描述
在这里插入图片描述
后置通知中,还可以通过配置获取目标方法的返回值。
在这里插入图片描述
异常通知:
在目标方法抛出异常时执行的通知
在这里插入图片描述
最终通知:
是在目标方法执行之后执行的通知。和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返-例如抛出异常,则后置通知不会执行。而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。另外,后置通知可以通过配置得到返回值,而最终通知无法得到。最终通知也可以额外接收一个JoinPoint参数,来获取目标对象和目标方法相关信息,但一定要保证必须是第一个参数。
在这里插入图片描述
在这里插入图片描述
当存在多个切面时:
采用了责任链设计模式,如下图。
在这里插入图片描述
如果目标方法抛出异常:
在这里插入图片描述

Spring AOP的原理过程

在初始化spring容器的时候,通过注解或者配置文件,把需要交给spring管理的类全部加载进去,然后创建对应的对象,这个时候根据AOP的配置,来判断哪些对象是需要代理对象的,这个时候把它对应的代理对象也创建出来,当目标对象调用到那些切入点的时候,其实是代理对象来进行调用的,而没有被注解或者配置文件特殊说明过的方法,则就正常调用。

Spring自动为目标对象生成代理对象,默认情况下,如果目标对象实现过接口,则采用java的动态代理机制,如果目标对象没有实现过接口,则采用cglib动态代理。
如果希望使用cglib的方式来生成代理对象,则在配置文件中<aop:config proxy-target-class=“true”>即可。

Spring实现AOP和综合案例(注解的方式)

直接结合综合案例来阐述如何使用
下面是项目结构:
在这里插入图片描述

扫描二维码关注公众号,回复: 11325725 查看本文章

首先需要在配置文件中开启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"
       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="cn.tdu"></context:component-scan>
    <!--注解声明-->
    <context:annotation-config></context:annotation-config>
    <!--AOP注解配置-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

要使用AOP,就需要写一个切面类和里面的通知,在切面类上用@Component和@Aspect来注解这个类,表明这是一个切面类。这里我使用@Around来注解这个通知,表示它是一个环绕通知。
其他几种通知的方式:
前置通知 @Before
环绕通知 @Around
后置通知 @AfterReturning
异常通知 @AfterThrowing
最终通知 @After
如果一个切面中多个通知 重复使用同一个切入点表达式,则可以将该切入点表达式使用@pointcut来单独定义,后续引用,注意,在当前切面中通过注解定义的切入点只在当前切面中起作用,其他切面看不到。

@Component
@org.aspectj.lang.annotation.Aspect
public class Aspect {
//切入点表达式
    @Pointcut("execution(cn.tdu.bean.User cn.tdu.Service.UserService.Create())")
    public void mx(){};
//该注解表示环绕增强mx()表达式筛出的方法
    @Around("mx()")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("正准备调用");
        Object obj=jp.proceed();
        System.out.println("调用完成");
        return obj;

    }
}

另外说几句,在后置通知的注解中,也可以额外配置一个returning属性,来指定一个参数名接受目标方法执行后的返回值。

在这里插入图片描述
在异常通知的注解中,也可以额外配置一个throwing属性,来指定一个参数名接受目标方法抛出的异常对象
在这里插入图片描述
上面就说明了如何使用注解来实现spring AOP 的使用,切面类,通知和切入点我们都已经写好了,再来看看其他类的代码。

首先来看DAO层:

@Component("MySql")
public class MySql implements dao {
//使用工厂注入user
    @Autowired
    @Qualifier("getInstance")
    User user=null;

    @Override
    public User insert() {
        System.out.println("创建成功");
        return user;
    }
}

该层实现了一个dao接口,之前说过这样做的目的是用来解耦的,user是一个对象,通过工厂模式生产出来的,用来返回出去到上一层。

工厂:

@Component("UserFactory")
public class UserFactory {
//通过user类来注入到工厂中,工厂返回
    @Autowired
    @Qualifier("User")
    User user=null;
    @Bean("getInstance")
    public User getInstance(){
        return this.user;
    }
}

User类:

@Component("User")
public class User {
    @Value("lxh")
    private String name;
    @Value("23")
    private int age;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

service层:
同理也是实现了一个接口来进行解耦合,这里和之前的案例没有多大区别就不做说明了。

@Component("UserService")
public class UserService implements service {

//注入Mysql的对象
    @Autowired
    @Qualifier("MySql")
    private dao Dao=null;

    @Override
    public User Create() {
    //调用mysql中的方法
        return Dao.insert();
    }
    public void test1(){
        System.out.println("不是代理对象调用的");
    }
}

web层:
这里和之前也没有多大区别也不做解释

@Component("control")
public class control {
    @Autowired
    @Qualifier("UserService")
    private service service=null;

    public User create(){
        return service.Create();
    }
    public void test1(){
        service.test1();
    }
}

调用测试:

public class test01 {
    @Test
    public void test(){
        ApplicationContext context=new ClassPathXmlApplicationContext("Application.xml");
        control con= (control) context.getBean("control");
        System.out.println(con.create());
        con.test1();
        ((ClassPathXmlApplicationContext)(context)).close();
    }
}

在这里插入图片描述
整个过程:
con.create()调用了service层中的create()方法,因为之前在切面类中已经把调用create()方法声明成了一个切入点(@Pointcut(“execution(cn.tdu.bean.User cn.tdu.Service.UserService.Create())”)),所以其实是UserService的代理对象调用的create方法,进入了环绕通知,执行环绕通知的前通知,然后手动调用方法(Object obj=jp.proceed();),于是进入到service层中的create方法,service层中的create方法再调用Dao层中的create方法,Dao层中的user通过注解获取到工厂生产的对象,然后把该对象返回给service,再执行环绕通知的后通知,然后再把对象返回给web层。

猜你喜欢

转载自blog.csdn.net/weixin_42596778/article/details/105905507
今日推荐