Java之Spring AOP入门到精通IDEA版(一篇文章精通系列)

Posted 蓝盒子itbluebox

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java之Spring AOP入门到精通IDEA版(一篇文章精通系列)相关的知识,希望对你有一定的参考价值。

一、设计模式-代理模式

代理模式:给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理
就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不
能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。

客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的
其他角色代为创建并传入。

为什么要使用代理模式呢?
第一,它有间接的特点,可以起到中介隔离作用。
就好比在租房的时候,房东可能不在本地,而短期内又不能赶回来,此时中介的出场,就作为房东的代理实现和我们签订承租合同。而我们和房东之间就没有耦合了。

第二,它有增强的功能。还以租房为例,我们首先考虑的是找一个靠谱的中介,由中介
给我们提供房源信息,并且告诉我们房屋的细节,合同的细节等等。
当然我们也可以自己去
找一些个人出租的房屋,但是在这之中,我们要了解租赁合同,房屋验收,租金监管等情
况,这无疑对我们是繁重的工作。
而有了中介作为我们的代理中间人,他把了解到的信息告诉我们,我们一样可以租到房子,而不必做那些繁重的工作。

二、AOP思想及实现原理

1、AOP思想

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2、实现原理

在上面的概念中描述出aop的实现原理是基于动态代理技术实现的。下面是针对动态代
理的一些介绍:
特点: 字节码随用随创建,随用随加载
分类: 基于接口的动态代理,基于子类的动态代理
作用: 不修改源码的基础上对方法增强

(1)基于接口的动态代理:

提供者是:JDK官方
使用要求:被代理类最少实现一个接口。
涉及的类:Proxy
创建代理对象的方法:newProxyInstance

方法的参数:
ClassLoader:类加载器。用于加载代理对象的字节码的。和被代理对象使用相同的类加载器。固定写法。
Class[]:字节码数组。用于给代理对象提供方法。和被代理对象具有相同的方法。

被代理类是一个普通类:被代理类对象.getClass().getInterfaces();

被代理类是一个接口:new Class[]{被代理了.class}它也是固定写法InvocationHanlder:要增强的方法。此处是一个接口,我们需要提供它的实现类。通常写的是匿名内部类。增强的代码谁用谁写。

基于子类的动态代理

提供者是:第三方cglib包,在使用时需要先导包(maven工程导入坐标即可)

使用要求:被代理类不能是最终类,不能被final修饰

涉及的类:Enhancer

创建代理对象的方法:create

方法的参数:

Class:字节码。被代理对象的字节码。可以创建被代理对象的子类,还可以获取被代理对象的类加载器。
Callback:增强的代码。谁用谁写。通常都是写一个接口的实现类或者匿名内部类。

Callback中没有任何方法,所以我们一般使用它的子接口:MethodInterceptor

3、Spring中AOP的术语

Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,指的是方法,因为spring只支持方法类型的连接点。

Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。

Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, 可以在运行期为类动态地添加一些方法或Field。

Target(目标对象):代理的目标对象。

Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。

Aspect(切面):是切入点和通知(引介)的结合。

三、Spring注解驱动AOP开发入门

1、写在最前

a.Spring的aop是基于ioc的。所以需要有spring的ioc基础。(本篇内容不对ioc进行讲解)
b.本章节我们只是对aop的使用做基本功能展示,目的是为了以此讲解aop中的注解和执行原理分析。

2、注解驱动入门案例介绍

需求:实现在执行service方法时输出执行日志。(除了业务层外,表现层和持久层也可以实现)

3、案例实现

  • 工程搭建


  • 引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.itbluebox</groupId>
    <artifactId>spring-aop</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--导入yaml文件解析器坐标-->
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.23</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
    </dependencies>
</project>
  • 创建User实体类

package cn.itbluebox.pojo;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {

    private String id;
    private String username;
    private String password;
    private String email;
    private Date birthday;
    private String gender;
    private String mobile;
    private String nickname;

    public User() {
    }

    public User(String id, String username, String password, String email, Date birthday, String gender, String mobile, String nickname) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
        this.birthday = birthday;
        this.gender = gender;
        this.mobile = mobile;
        this.nickname = nickname;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\\'' +
                ", username='" + username + '\\'' +
                ", password='" + password + '\\'' +
                ", email='" + email + '\\'' +
                ", birthday=" + birthday +
                ", gender='" + gender + '\\'' +
                ", mobile='" + mobile + '\\'' +
                ", nickname='" + nickname + '\\'' +
                '}';
    }
}

  • 业务层接口

package cn.itbluebox.service;

import cn.itbluebox.pojo.User;

public interface UserService {

    /*
    保存用户
     */
    void save(User user);

}

  • 业务层接口实现类

package cn.itbluebox.service.impl;

import cn.itbluebox.pojo.User;
import cn.itbluebox.service.UserService;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserServiceImpl implements UserService {
    public void save(User user) {
        System.out.println("保存用户信息:"+user);
    }
}

  • 日志工具类

package cn.itbluebox.utils;

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

@Component
@Aspect
public class LogUtil {

    /*
    Pointcut通用切入点表达式
    execution允许
     */
    @Pointcut("execution(* cn.itbluebox.service.impl.*.*(..))")
    private void pt1(){}


    /*
    前置通知
     */
    @Before("pt1()")
    public void beforeLog(){
        System.out.println("执行切入点方法前记录日志");
    }
    /*
    后置通知
     */
    @AfterReturning("pt1()")
    public void afterReturningLog(){
        System.out.println("正常执行切入点方法后记录日志");
    }


    /*
    异常通知
     */
    @AfterReturning("pt1()")
    public void afterThrowingLog(){
        System.out.println("执行切入点方法产生异常后记录日志");
    }

    /*
    最终通知
     */
    @After("pt1()")
    public void afterLog(){
        System.out.println("无论切入点方法执行是否异常都记录日志");
    }
    /*
    环绕通知
     */
    @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
            //1.定义返回值
        Object rtValue = null;
        try{
            //前置通知
            System.out.println("执行切入点方法前记录日志");
            //2.获取方法执行所需的参数
            Object[] args = pjp.getArgs();
            //3.执行切入点方法
            rtValue = pjp.proceed(args);
            //后置通知
            System.out.println("正常执行切入点方法后记录日志");
        }catch (Throwable t){
            //异常通知
            System.out.println("执行切入点方法产生异常后记录日志"+t);
        }finally {
            //最终通知
            System.out.println("无论切入点方法执行是否有异常都记录日志");
        }
        return rtValue;
    }

}

  • 创建配置类:config.SpringConfiguration

package cn.itbluebox.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("cn.itbluebox")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

  • 创建测试类:

package cn.itbluebox.test;

import cn.itbluebox.config.SpringConfiguration;
import cn.itbluebox.pojo.User;
import cn.itbluebox.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringAOPTest {

    public static void main(String[] args) {

        //1、获取容器
        AnnotationConfigApplicationContext
                ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2、获取Bean对象
        UserService userService = ac.getBean("userService", UserService.class);

        //3、准备数据
        User user = new User();
        user.setId("1");
        user.setUsername("test");
        user.setNickname("张三");
        //4、执行方法
        userService.save(user);
    }

}

运行测试类

4、可以将切入点精确到方法

  • 在接口当中创建update方法

    public void update(User user) {
        System.out.println("保存用户信息:"+user)以上是关于Java之Spring AOP入门到精通IDEA版(一篇文章精通系列)的主要内容,如果未能解决你的问题,请参考以下文章

Java之Spring Boot入门到精通IDEA版SpringBoot原理分析,SpringBoot监控(一篇文章精通系列)下

Java之Spring入门到精通IDEA版IoC和DI注解开发(一篇文章精通系列)

Java之Spring入门到精通IDEA版IoC和DI注解开发(一篇文章精通系列)

Java之Spring入门到精通IDEA版Spring的IoC和DI(一篇文章精通系列)

Java之Spring入门到精通IDEA版Spring的IoC和DI(一篇文章精通系列)

Java之Spring Boot入门到精通IDEA版(一篇文章精通系列)上