spring_AOP

Posted doaoao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring_AOP相关的知识,希望对你有一定的参考价值。

AOP

我们先创建下方一个简单的程序,再从长远角度来看,该程序存在的问题

# 比如我们创建一个可以记录日志的工具类,然后在我们实际开发应用过程中使用这个工具类,代码如下

1:创建一个记录日志的工具类

package com.doaoao.util;

public class TestLog {
    // 记录日志
    public static void doLog(Class<?> c){
        System.out.println("记录日志"+ c.getName());
    }
}

2:在实际应用中使用这个工具类

package com.doaoao.impl;
import com.doaoao.dao.UserDao;
import com.doaoao.util.TestLog;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        
        // 记录日志代码
        TestLog.doLog(this.getClass());
        
        // 实际的业务
        System.out.println("实际操作");
    }
}

 

注:上方直接将日志代码添加在业务逻辑中,会导致这个工具了侵入到业务逻辑的代码中,如果我们有多个业务逻辑,比如实现某个数据库的数据的增删改查,我们就必须在每个业务逻辑中添加该代码,当修改日志代码时,就得在所有的方法中将代码进行修改,可维护性太差了。同时页增加了业务的开发难度

## 使用动态代理

使用动态代理,可以让之前遇到的问题得以解决,上代码

1:创建动态代理类

package com.doaoao.proxy;

import com.doaoao.util.TestLog;

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

public class TestInvocationHandler implements InvocationHandler {

    // 添加成员变量(使用Object类型可以处理所有类型 userDao,studentDao...)
    private Object object;

    // 添加构造方法将要增强的类的对象传入
    public TestInvocationHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 记录日志
        TestLog.doLog(object.getClass());

        // 业务处理
        method.invoke(object,args);

        return null;
    }
}

2:修改之前的类(修改后你会发现,世界是多么的清净)

package com.doaoao.impl;

import com.doaoao.dao.UserDao;

public class UserDaoImpl implements UserDao {

    @Override
    public void addUser() {
        // 实际的业务
        System.out.println("实际操作");
    }
}

3:创建测试方法

package com.doaoao.test;
import com.doaoao.dao.UserDao;
import com.doaoao.impl.UserDaoImpl;
import com.doaoao.proxy.TestInvocationHandler;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Proxy;
public class test01 {

    @Test
    public void testProxy(){
        // 创建目标类对象
        UserDao userDao = new UserDaoImpl();

        // 创建代理
        UserDao userDaoPeoxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),new TestInvocationHandler(userDao));

        // 调用目标对象中的方法
        userDaoPeoxy.addUser();
    }
}

## AOP

# AOP(Aspect Orient Programming),面向切面编程;我们都知道Java是一种面向对象编程语言,其实这两种之间是没有冲突的,反而面向切面编程是面向对象编程的一种补充。在程序运行过程中,动态的将代码切入到类的指定位置(如上方的代码切入到业务处理之前),指定位置的编程思想就是面向切面编程。

# AOP的最大好处就是可以很好的分离业务代码和非业务代码,降低代码之间的耦合,提高代码的复用性

# 我们来回顾一下spring为了简化Java开发所采取的4中方法

  1:基于POJO的轻量级和最小侵入性编程

  2:通过依赖注入和面向接口实现松耦合

  3:基于切面惯例进行声明式编程

  4:通过切面和模板减少样板式代码

其中面向切面就是简化Java开发的一种重要的方法

# spring底层使用两种代理模式来实现AOP

  1:JDK的动态代理(如果目标类实现接口,spring默认使用JDK的动态代理)

    底层使用反射,效率较低,JDK动态代理由JDK提供,不需要再额外添加Jar包

  2:CGLIB的动态代理(不管目标类没有实现接口,spring 都会使用CGLIB动态代理)

    底层使用字节码处理器,效率强于JDK的动态代理,需要添加字节码处理框架ASM,也需要CGLIB框架

AOP中常用的术语

1:目标对象(Target)
目标对象指将要被增强的对象,即包含主业务逻辑的类的对象。上例中的 UserDaoImpl 的对象若被增强,则该类称为目标类,UserDaoImpl实例化的类为目标类

2:切面(Aspect)
切面泛指非业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面有通知

3:织入(Weaving)
织入是指将切面代码插入到目标对象的过程。上例中 MyInvocationHandler 类中的 invoke()
方法完成的工作,就可以称为织入。

4:连接点(JoinPoint)
连接点指可以被切面织入的方法,如之前例子中的 addUser ,通常业务接口中的方法均为连接点。

5:切入点(Pointcut)
切入点指切面具体织入的方法。在 UserDaoImpl 类中,若 addUser()被增强,而doOther()不被增强,则 addUser()为切入点,而 doOther()仅为连接点。 被标记为 final 的方法是不能作为连接点与切入点的,因为是不能被修改的,不能被增强的。

6:通知(Advice)

指定切面在切入点的之前还是之后执行
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。上例中的MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。Advice有下面几种,这里使用常用的AspectJ方式:

  前置通知(Before advice):在连接点之前执行,即目标方法执行之前执行。

  后置通知(After returning advice):在连接点正常结束之后执行,如果连接点抛出异常,则不执行。

  异常通知(After throwing advice):在连接点抛出异常后执行

  最终通知(After (finally) advice):在连接点结束之后执行,无论是否抛出异常,都会执行。

  环绕通知(Around advice):在连接点之前和之后均执行。

7:aop代理(AOP proxy)
spring中的aop代理有两种:jdk自带的动态代理和CGLIB代理。

AspectJ 对 AOP 的实现

 Aspect和框架和spring都对AOP进行了实现,Aspect的实现方法更为方便快捷。支持注解式开发

## 基于xml实现AOP的方式

1:添加相应的依赖jar包

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.0.4.RELEASE</version>
</dependency>

2:创建UserService接口

package com.doaoao.dao;

public interface UserService {
    void addUser();
    void selectUserById(int id) throws Exception;
    int updateUser();
    void deleteUser();
    void selectUser();
}

3:创建UserService接口的实现类

package com.doaoao.impl;

import com.doaoao.dao.UserService;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("执行addUser方法");
    }

    @Override
    public void selectUserById(int id) throws Exception {
        System.out.println("执行selectUserById方法");
        if(id == 0){
            throw new Exception();
        }
    }

    @Override
    public int updateUser() {
        System.out.println("执行updateUser方法");
        return 1;
    }

    @Override
    public void deleteUser() {
        System.out.println("执行deleteUser方法");
    }

    @Override
    public void selectUser() {
        System.out.println("执行selectUser方法");
    }
}

4:创建切面类MyAspect

package com.doaoao.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

public class MyAspect {
    public void before(){
        System.out.println("前置通知");
    }

    public void after(){
        System.out.println("最终通知");
    }

    public void afterThrowing(Exception e){
        System.out.println("异常通知" + e);
    }

    public void afterReturning(int result){
        System.out.println("后置通知" + result);
    }

    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("前环绕通知");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("前环绕通知");

        return proceed;
    }
}

5:添加spring的配置文件,此处命名为 spring-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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">


    <!-- 注册bean -->
    <bean id="userService" class="com.doaoao.impl.UserServiceImpl" />
    <bean id="myAspect" class="com.doaoao.aspect.MyAspect" />


    <!-- 配置aop -->
    <aop:config>
        <!-- 定义切入点 -->
        <!-- *表示方法可以由任意返回值 -->
     <!-- id表示切入点的名称,expression表示切入点的值(其利用execution表达式) --> <aop:pointcut id="addUserPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl.addUser())" /> <aop:pointcut id="selectUserPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl..selectUser())"/> <!-- ".."表示可以有参数也可以没有参数 --> <aop:pointcut id="selectUserByIdPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl.selectUserById(..))"/> <aop:pointcut id="updateUserPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl.updateUser())"/> <aop:pointcut id="deleteUserPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl.deleteUser())"/> <!-- 定义切面 --> <!-- 引用到我们上方定义的bean id 表示应用于哪个切面-->
     <!-- method:指定通知该切面中的哪个方法 pointcut-ref:指定该通知要织入的切入点 --> <aop:aspect ref="myAspect"> <!-- 前置通知 --> <aop:before method="before" pointcut-ref="addUserPointcut"/> <!-- 后置通知 "result"参数要与类中定义的参数名一致 --> <aop:after-returning method="afterReturning" pointcut-ref="updateUserPointcut" returning="result"/> <!-- 异常通知 "e"要与类中定义的参数一致,当抛异常时会调用到该方法 --> <aop:after-throwing method="afterThrowing" pointcut-ref="selectUserByIdPointcut" throwing="e"/> <!-- 最终通知 --> <aop:after method="after" pointcut-ref="selectUserPointcut"/> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="deleteUserPointcut"/> </aop:aspect> </aop:config> </beans>
<aop:before/>:前置通知 
<aop:after-returning/>:  后置通知 
<aop:around/>:环绕通知 
<aop:after-throwing/>:异常通知 
<aop:after/>:最终通知

6:创建测试类

package com.doaoao.test;

import com.doaoao.dao.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    @Test
    public void new_test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop.xml");
        UserService userService = (UserService)context.getBean("userService");
        userService.addUser();

        userService.selectUser();

        try {
            // 传0时抛异常
            userService.selectUserById(0);
        } catch (Exception e) {
            e.printStackTrace();
        }

        userService.updateUser();

        userService.deleteUser();
    }

}

## AspectJ 的切入点表达式

execution ( 
    [modifiers-pattern]  访问权限类型
    ret-type-pattern  返回值类型
    [declaring-type-pattern]  全限定性类名
    name-pattern(param-pattern)  方法名(参数名)
    [throws-pattern]  抛出异常类型 
)

# 注:上方被[] 包裹的可以省略不写,而其它的都得写,各个部分以空格隔开

例子:

execution(public * *(..)) 
// 指定切入点为:任意public方法,返回值类型任意(第一个*号),任意方法名(第二个*号),参数可有可无(..)

execution(* set*(..)) 
// 指定切入点为:任何一个以“set”开始的方法,

execution(* com.doaoao.*.*(..)) 
// 指定切入点为:任意返回值类型,定义在 service 包里的任意类中的任意方法(com.doaoao.*.*)

execution(* com.xyz.service..*.*(..))
// 指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。

execution(* *.service.*.*(..))
// 指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点 

execution(* *..service.*.*(..))
// 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点 

execution(* *.ISomeService.*(..))
// 指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点 

execution(* *..ISomeService.*(..))
// 指定所有包下的 ISomeSerivce 接口中所有方法为切入点 

execution(* com.xyz.service.IAccountService.*(..)) 
// 指定切入点为:  IAccountService  接口中的任意方法。 

execution(* com.xyz.service.IAccountService+.*(..)) 
// 指定切入点为:  IAccountService  若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。 

execution(* joke(String,int)))
// 指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参    数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。 

execution(* joke(String,*))) 
// 指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2)都是,但 joke(String s1,double d2,String s3)不是。

execution(* joke(String,..)))   
// 指定切入点为:所有的 joke()方法,该方法第  一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(Strings1,double d2,String s3)都是。

execution(* joke(Object))
// 指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。

execution(* joke(Object+))) 
// 指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。

## 基于注解的AOP 实现方式

1:添加对应依赖

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.0.4.RELEASE</version>
    </dependency>

2:添加接口(与上方相同)

package com.doaoao.dao;

public interface UserService {
    void addUser();
    void selectUserById(int id) throws Exception;
    int updateUser();
    void deleteUser();
    void selectUser();
}

3:添加接口的实现类(与上方相同)

package com.doaoao.impl;

import com.doaoao.dao.UserService;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("执行addUser方法");
    }

    @Override
    public void selectUserById(int id) throws Exception {
        System.out.println("执行selectUserById方法");
        if(id == 0){
            throw new Exception();
        }
    }

    @Override
    public int updateUser() {
        System.out.println("执行updateUser方法");
        return 1;
    }

    @Override
    public void deleteUser() {
        System.out.println("执行deleteUser方法");
    }

    @Override
    public void selectUser() {
        System.out.println("执行selectUser方法");
    }
}

4:添加切面类(不同于上方)

package com.doaoao.aspect;

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

@Aspect
@Component
public class MyAspect {

    @Before("execution(* *..UserServiceImpl.addUser())")
    public void before(){
        System.out.println("前置通知");
    }

    @After("execution(* *..UserServiceImpl.selectUser())")
    public void after(){
        System.out.println("最终通知");
    }

    @AfterThrowing(value = "execution(* *..UserServiceImpl.selectUserById(..))" ,throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("异常通知" + e);
    }

    @AfterReturning(value = "execution(* *..UserServiceImpl.updateUser())",returning = "result")
    public void afterReturning(int result){
        System.out.println("后置通知" + result);
    }

    @Around(value = "execution(* *..UserServiceImpl.deleteUser())")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("前环绕通知");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("前环绕通知");

        return proceed;
    }
}

5:添加配置文件(不同于上方)

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.doaoao" />
    <!-- 配置AspectJ自动代理 -->
    <aop:aspectj-autoproxy />
</beans>

 6:添加测试方法(与上方相同)

package com.doaoao.test;

import com.doaoao.dao.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    @Test
    public void new_test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop.xml");
        UserService userService = (UserService)context.getBean("userService");
        userService.addUser();

        userService.selectUser();

        try {
            // 传0时抛异常
            userService.selectUserById(0);
        } catch (Exception e) {
            e.printStackTrace();
        }

        userService.updateUser();

        userService.deleteUser();
    }

}

...待补充

本笔记参考自:小猴子老师教程 http://www.monkey1024.com

以上是关于spring_AOP的主要内容,如果未能解决你的问题,请参考以下文章

Spring_AOP 记录系统关键操作日志用法

Spring_AOP的实现机制-动态代理

Spring_Aop的xml和注解的使用

spring_aop注解

Spring_Aop_

Spring_Aop