栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring:四、AOP

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring:四、AOP

Spring:四、AOP

1 介绍

Spring有两个核心部分:IoC和AOP。IoC即控制反转,把创建对象过程交给Spring进行管理。AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,和 OOP(面向对象编程)类似,也是一种编程思想。AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。

Spring AOP 是基于 AOP 编程模式的一个框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。有两种实现方式:基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。

AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。

2 AOP底层原理

AOP底层使用动态代理

(1)两种情况动态代理

一、有接口情况,使用JDK动态代理:创建接口实现类代理对象,增强类的方法。使用JDK动态代理,需使用Proxy类里面的方法创建代理对象。

二、无接口情况,使用CGLIB动态代理:创建子类的代理对象,增强类的方法。

(一)JDK动态代理:

查看Proxy类,地址:https://www.matools.com/api/java8

调用Proxy的静态方法newProxyInstance,获取指定接口的代理类实例

newProxyInstance(ClassLoader loader, 类[] interfaces, InvocationHandler h)

方法参数3个:

1 类加载器
2 增强方法所在的类,这个类实现的接口,支持多个接口
3 实现接口InvocationHandler,创建代理对象,写增强的方法

编写JDK动态代理代码:
(1)创建接口,定义方法

package com.xiaoxu;


public interface UserDao {
    public int add(int a,int b);
    public String update(String id);
}

(2)创建接口实现类,实现方法

package com.xiaoxu;


public class UserDaoImpl implements UserDao{

    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public String update(String id) {
        return id;
    }
}

(3)使用Proxy类创建接口代理对象

可以使用匿名内部类:

package com.xiaoxu;

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


public class JDKProxy {
    public static void main(String[] args) {
        //要实现的接口,是Class数组
        Class[] interfaces={UserDao.class};
        //创建接口实现类代理对象
        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces, new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return null;
            }
        });
    }
}

也可以写一个类来实现:

package com.xiaoxu;

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


public class JDKProxy {
    public static void main(String[] args) {
        //要实现的接口,是Class数组
        System.out.println(Arrays.toString(UserDaoImpl.class.getInterfaces()));
        //可以把Proxy.newProxyInstance的第二个参数:interfaces,替换成UserDaoImpl.class.getInterfaces()
        Class[] interfaces={UserDao.class};
        //创建接口实现类代理对象
//        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces, new InvocationHandler(){
//            @Override
//            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                return null;
//            }
//        });
        UserDaoImpl userDao=new UserDaoImpl();
        UserDao u=(UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
        System.out.println(u.add(1, 5));
        System.out.println(u.update("123"));
    }
}

class UserDaoProxy implements InvocationHandler{
    //创建的是谁的代理对象,把谁传递过来
    //有参构造传递
    private Object obj;
    public UserDaoProxy(Object obj){
        this.obj=obj;
    }
    //增强的逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法之前执行
        System.out.println("方法之前执行..."+method.getName()+":传递的参数:"+ Arrays.toString(args));
        //被增强的方法执行
        Object o=method.invoke(obj,args);
        //方法之后执行
        System.out.println("方法之后执行..."+obj);
        return o;
    }
}

执行JDKProxy的main方法,可知代理类中传入了接口实现类对象,那么接口实现类对象实现的接口方法,全部都增强了:

[interface com.xiaoxu.UserDao]
方法之前执行...add:传递的参数:[1, 5]
方法之后执行...com.xiaoxu.UserDaoImpl@3764951d
6
方法之前执行...update:传递的参数:[123]
方法之后执行...com.xiaoxu.UserDaoImpl@3764951d
123

(二)CGLIB动态代理:

JDK 动态代理的目标类必须要实现一个或多个接口,具有一定的局限性。如果不希望实现接口,可以使用 CGLIB代理。

CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。使用 CGLIB 需要导入 CGLIB 和 ASM 包,即 asm-x.x.jar 和 CGLIB-x.x.x.jar 。如果您已经导入了 Spring 的核心包 spring-core-x.x.x.RELEASE.jar,就不用再导入 asm-x.x.jar 和 cglib-x.x.x.jar 了。

Spring 核心包中包含 CGLIB 和 asm,也就是说 Spring 核心包已经集成了 CGLIB 所需要的包,所以在开发中不需要另外导入asm-x.x.jar 和 cglib-x.x.x.jar 包了。

对于类实现接口,可以看做是特殊的类继承,可看做接口是父类,接口实现类是子类:
UserDao:

package com.xiaoxu;


public interface UserDao {
    public int add(int a,int b);
    public String update(String id);
}

UserDaoImpl:

package com.xiaoxu;


public class UserDaoImpl implements UserDao {

    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public String update(String id) {
        return id;
    }
}

CGLIBProxy:

package com.xiaoxu;


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Arrays;


public class CGLIBProxy implements MethodInterceptor {
    private Object obj;
    public CGLIBProxy(Object obj){
        this.obj=obj;
    }
    
    //CGLIB中参数是Object[] objects,和JDK动态代理有一定的区别
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //方法之前执行
        System.out.println("CGLIB:方法之前执行..."+method.getName()+":传递的参数:"+ Arrays.toString(objects));
        //被增强的方法执行
        Object o1=method.invoke(obj,objects);
        //方法之后执行
        System.out.println("CGLIB:方法之后执行..."+obj);
        return o1;
    }

    public static void main(String[] args) {
        Enhancer e=new Enhancer();
        //设置父类,因为CGLIB是针对指定的类生成一个子类,所以需要指定父类
        e.setSuperclass(UserDaoImpl.class);
        e.setCallback(new CGLIBProxy(new UserDaoImpl()));//设置回调,传入代理对象
        UserDao o= (UserDao) e.create();
        System.out.println(o.add(1,6));
        System.out.println(o.update("34"));
    }
}
CGLIB:方法之前执行...add:传递的参数:[1, 6]
CGLIB:方法之后执行...com.xiaoxu.UserDaoImpl@22927a81
7
CGLIB:方法之前执行...update:传递的参数:[34]
CGLIB:方法之后执行...com.xiaoxu.UserDaoImpl@22927a81
34

3 AOP术语

(1)连接点

类里面哪些方法可以被增强,这些方法称为连接点

(2)切入点

实际被真正增强的方法,被称为切入点

(3)通知(增强)

(3.1)实际增强的逻辑部分被称为通知(增强)

(3.2)通知有多种类型:前置通知、后置通知、环绕通知、异常通知、最终通知

(4)切面

动作,把通知应用到切入点过程

4 AOP操作

(1)Spring框架一般是基于AspectJ实现AOP操作

AspectJ:不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作

(2)基于AspectJ实现AOP操作

(2.1)基于XML配置文件实现

(2.2)基于注解方式实现(使用)

(3)引入AOP相关依赖

若已经引入了spring-webmvc依赖(包含spring-aop):


    org.springframework
    spring-webmvc
    5.3.12

然后引入aspectJ相关依赖:


    org.springframework
    spring-aspects
    5.2.13.RELEASE


    org.aspectj
    aspectjweaver
    1.9.5

导入aspectjweaver依赖,就可以使用@Aspect注解修饰代理类,@Before等修饰增强的方法(均在org.aspectj.lang.annotation下)

(4)切入点表达式

(4.1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强

(4.2)语法结构:

execution([权限修饰符][返回类型][类全路径][方法名称] ([参数列表]))

其中权限修饰符是可以省略的,例如:execution (* com.xiaoxu.service . .* . *(. .))

整个表达式可以分为五个部分:

1、execution(): 表达式主体。

2、第一个*号:表示返回类型,*号表示所有的类型。

3、包名:表示需要拦截的包名,即com.xiaoxu.service,后面的两个句点表示当前包和当前包的所有子包,这样可以选择性覆盖com.xiaoxu.service包和子包下所有类的方法。

4、第二个*号:表示类名,*号表示所有的类。

5、*(. .):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示方法的任意参数。

还可以具体到某个类中的某个方法进行AOP增强,例如:

execution(*com.xiaoxu.dao.UserDao.add(. .)),意思是权限修饰符直接省略,然后返回类型不可省略,*代表全部返回类型,对于com.xiaoxu.dao包下的UserDao类中的add方法进行增强,add方法的参数由两个点表示。

(5)AspectJ注解


(5.1)创建类,在类里面定义方法

package com.xiaoxu.aopano;

public class User {
    public void add(){
        System.out.println("add......");
    }
}

(5.2)创建增强类(编写增强逻辑)

在增强类中,创建方法,让不同方法代表不同通知类型

package com.xiaoxu.aopano;

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

(5.3)进行通知的配置

在spring的配置文件中,开启注解扫描;使用注解创建User和UserProxy对象;在增强类上面添加注解@Aspect;在Spring配置文件中开启生成代理对象

步骤如下:

1.context命名空间

2.aop命名空间

3. 开启注解扫描-xml



        
        


4.使用@Component注解创建User和UserProxy对象


5. 代理类(增强类)加上@Aspect注解

package com.xiaoxu.aopano;

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

@Component
@Aspect  //生成代理对象
public class UserProxy {
    //前置通知
    public void before(){
        System.out.println("before...");
    }
}

6.开启AspectJ生成代理对象-xml



        
        
        
        
        


7.配置不同类型的通知

在增强类里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置

package com.xiaoxu.aopano;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect  //生成代理对象
public class UserProxy {
    //前置通知
    //before注解表示作为前置通知
    @Before(value = "execution(* com.xiaoxu.aopano.User.add(..))")
    public void before(){
        System.out.println("before...");
    }
}

8.编写测试类

import com.xiaoxu.aopano.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAopUser {
    @Test
    public void test_01(){
        ApplicationContext context =new ClassPathXmlApplicationContext("MyAOP.xml");
        User u=context.getBean("user",User.class);
        u.add();
    }
}

结果:

before...
add......

9.增加新的类型通知

package com.xiaoxu.aopano;

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

@Component
@Aspect  //生成代理对象
public class UserProxy {
    //前置通知
    //before注解表示作为前置通知
    @Before(value = "execution(* com.xiaoxu.aopano.User.add(..))")
    public void before(){
        System.out.println("before...");
    }

    @AfterReturning(value = "execution(* com.xiaoxu.aopano.User.add(..))")
    public void afterReturning(){
        System.out.println("afterReturning...");
    }

    @After(value = "execution(* com.xiaoxu.aopano.User.add(..))")
    public void after(){
        System.out.println("after...");
    }

    @AfterThrowing(value = "execution(* com.xiaoxu.aopano.User.add(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing...");
    }

    //环绕通知
    @Around(value = "execution(* com.xiaoxu.aopano.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("环绕之前...");
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后...");
    }
}

重新执行测试类:

环绕之前...
before...
add......
afterReturning...
after...
环绕之后...

afterThrowing是异常通知,有异常才会通知,修改User.add:

public void add(){
    int i=26/0;
    System.out.println("add......");
}


可见抛出异常后,后面的通知也不会执行了。

10.公共切入点抽取

修改UserProxy :

package com.xiaoxu.aopano;

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

@Component
@Aspect  //生成代理对象
public class UserProxy {

    //相同切入点抽取
    @Pointcut(value = "execution(* com.xiaoxu.aopano.User.add(..))")
    public void commonPoint(){

    }

    //前置通知
    //before注解表示作为前置通知
    @Before(value = "commonPoint())")
    public void before(){
        System.out.println("before...");
    }

    @AfterReturning(value = "commonPoint())")
    public void afterReturning(){
        System.out.println("afterReturning...");
    }

    @After(value = "commonPoint())")
    public void after(){
        System.out.println("after...");
    }

    @AfterThrowing(value = "commonPoint())")
    public void afterThrowing(){
        System.out.println("afterThrowing...");
    }

    //环绕通知
    @Around(value = "commonPoint())")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("环绕之前...");
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后...");
    }
}

重新执行,效果一致:

环绕之前...
before...
add......
afterReturning...
after...
环绕之后...

11.有多个增强类用于增强相同的方法,可设置增强类优先级

在增强类上增加@Order(数字类型值),数字越小,优先级越高

UserProxy :

package com.xiaoxu.aopano;

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

@Component
@Aspect  //生成代理对象
@Order(22)
public class UserProxy {

    //相同切入点抽取
    @Pointcut(value = "execution(* com.xiaoxu.aopano.User.add(..))")
    public void commonPoint(){

    }

    //前置通知
    //before注解表示作为前置通知
    @Before(value = "commonPoint())")
    public void before(){
        System.out.println("before...");
    }

    @AfterReturning(value = "commonPoint())")
    public void afterReturning(){
        System.out.println("afterReturning...");
    }

    @After(value = "commonPoint())")
    public void after(){
        System.out.println("after...");
    }

    @AfterThrowing(value = "commonPoint())")
    public void afterThrowing(){
        System.out.println("afterThrowing...");
    }

    //环绕通知
    @Around(value = "commonPoint())")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("环绕之前...");
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后...");
    }
}

PersonProxy :

package com.xiaoxu.aopano;

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

@Aspect
@Component
@Order(11)
public class PersonProxy {
    //相同切入点抽取
    @Pointcut(value = "execution(* com.xiaoxu.aopano.User.add(..))")
    public void commonPoint(){

    }

    //前置通知
    //before注解表示作为前置通知
    @Before(value = "commonPoint())")
    public void before(){
        System.out.println("Person before...");
    }

    @AfterReturning(value = "commonPoint())")
    public void afterReturning(){
        System.out.println("Person afterReturning...");
    }

    @After(value = "commonPoint())")
    public void after(){
        System.out.println("Person after...");
    }

    @AfterThrowing(value = "commonPoint())")
    public void afterThrowing(){
        System.out.println("Person afterThrowing...");
    }

    //环绕通知
    @Around(value = "commonPoint())")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("Person 环绕之前...");
        proceedingJoinPoint.proceed();
        System.out.println("Person 环绕之后...");
    }
}
Person 环绕之前...
Person before...
环绕之前...
before...
add......
afterReturning...
after...
环绕之后...
Person afterReturning...
Person after...
Person 环绕之后...

12.全注解运行AOP

AopConfig:

package com.xiaoxu.aopconfig;

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

@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = {"com.xiaoxu.aopano"})
public class AopConfig {
}

增加注解@ComponentScan(basePackages = {“com.xiaoxu.aopano”}),是为了代替如下xml的注解扫描,可获取到com.xiaoxu.aopano包下,@Component修饰的User和UserProxy类对象:



启用 @AspectJ 注解有以下两种方法:

(1)使用@Configuration和@EnableAspectJAutoProxy注解

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}

(2)基于XML配置

在xml中如下配置:



编写测试类:

import com.xiaoxu.aopano.User;
import com.xiaoxu.aopconfig.AopConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestAopUser {
    @Test
    public void test_02(){
        ApplicationContext context2=new AnnotationConfigApplicationContext(AopConfig.class);
        User u2=context2.getBean("user",User.class);
        u2.add();
    }
}

执行结果(和xml配置运行的AOP效果一致):

Person 环绕之前...
Person before...
环绕之前...
before...
add......
afterReturning...
after...
环绕之后...
Person afterReturning...
Person after...
Person 环绕之后...

(6)AspectJ配置文件(了解)

常用还是(5)中AspectJ的注解方式,配置文件仅做了解

(6.1)创建两个类,增强类和被增强类,创建方法

package com.xiaoxu.aopxml;

public class Book {
    public void buy(){
        System.out.println("buy...");
    }
}
package com.xiaoxu.aopxml;

public class BookProxy {
    public void before(){
        System.out.println("book before...");
    }
}

(6.2)在spring配置文件中创建两个类对象

(6.3)在spring配置文件中配置切入点

AspectJ支持的5种通知类型如下:


    
    
    
    
    
    
    
    
    
    

MyAOP2.xml:



        
        
        
        
        
            
            
            
            
                
            
        


(6.4)编写测试类

import com.xiaoxu.aopxml.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAopBook {
    @Test
    public void test_01(){
        ApplicationContext context=new ClassPathXmlApplicationContext("MyAOP2.xml");
        Book b=context.getBean("book",Book.class);
        b.buy();
    }
}

执行结果如下:

book before...
buy...

(7)总结

java的AOP,效果和python的装饰器一致,常用于登录鉴权、添加日志等等。不过区别有许多:java的AOP原理是动态代理,而python的装饰器原理是闭包。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/602881.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号