文档章节

spring基础知识---AOP动态代理原理

spinachgit
 spinachgit
发布于 02/21 17:23
字数 1667
阅读 213
收藏 11

Spring Boot实践——Spring AOP实现之动态代理

Spring AOP 介绍

  AOP的介绍可以查看 Spring Boot实践——AOP实现

  与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

  Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

  如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,是利用asm开源包,可以在运行时动态的生成某个类的子类。注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

  这里有注意的几点如下:

  • 从Spring 3.2以后不再将CGLIB放在项目的classpath下,而是将CGLIB类打包放在spring-core下面的org.springframework中。这个就意味着基于CGLIB的动态代理与JDK的动态代理在支持“just works”就一样了。
  • 在Spring 4.0中,因为CGLIB代理实例是通过Objenesis创建的,所以代理对象的构造器不再有两次调用。
  • 在 Spring Boot 2.0 中,Spring Boot现在默认使用CGLIB动态代理(基于类的动态代理), 包括AOP. 如果需要基于接口的动态代理(JDK基于接口的动态代理) , 需要设置spring.aop.proxy-target-class属性为false。

 

实现方式

一、验证Spring AOP动态代理

博主使用的是Spring Boot 2.0版本

1、JDK动态代理

定义接口

public interface Person {
    String say(String name);
    
    void eat(String food);
}

实现类

复制代码

@Component
public class Chinese implements Person {
    private Logger logger = LoggerFactory.getLogger(Person.class);
    
    public Chinese() {
        super();
        logger.info("Chinese ==> Chinese method : 正在生成一个Chinese实例");
    }

    @Override
    @PersonAnnotation(name="Chinese")//该注解是用来定义切点
    public String say(String name) {
        logger.info("Chinese ==> say method : say {}", name);
        return name + " hello, JDK implement AOP";
    }

    @Override
    public void eat(String food) {
        logger.info("Chinese ==> eat method : eat {}", food);
    }
    
}

复制代码

定义Aspect

复制代码

@Aspect
@Component
public class PersonAspect {
    private Logger logger = LoggerFactory.getLogger(PersonAspect.class);
    
    @Pointcut("@annotation(com.only.mate.springboot.annotation.PersonAnnotation)")
    public void pointCut(){
        
    }
    
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) throws Throwable {
        logger.info("PersonAspect ==> before method : {}", joinPoint.getClass());

    }
    
    @After("pointCut()")
    public void after(JoinPoint joinPoint){
        logger.info("PersonAspect ==> after method : {}", joinPoint.getClass());
    }
}

复制代码

 

2、CGLIB动态代理

定义一个类

复制代码

@Component
public class American {
    private Logger logger = LoggerFactory.getLogger(American.class);
    
    public American() {
        super();
        logger.info("American ==> American method : 正在生成一个American实例");
    }

    @PersonAnnotation(name="American")//该注解是用来定义切点
    public String say(String name) {
        logger.info("American ==> say method : say {}", name);
        return name + " hello, CGLIB implement AOP";
    }

    public void eat(String food) {
        logger.info("American ==> eat method : eat {}", food);
    }
    
}

复制代码

3、配置

<!-- 自动扫描使用了aspectj注解的类 -->
<aop:aspectj-autoproxy/>

@Configuration
@ComponentScan("com.only.mate.springboot.aop")
@EnableAspectJAutoProxy//开启AspectJ注解
public class CustomAopConfigurer {
}

4、运行

复制代码

@Controller
@RequestMapping(value="/aop")
public class AopImplementController {
    private Logger logger = LoggerFactory.getLogger(AopImplementController.class);
    
    @Autowired
    private Person chinese;
    @Autowired
    private American american;
    
    @ResponseBody
    @RequestMapping(value="/talk")
    public String talk() {
        chinese.say("中国人说汉语");
        american.say("American say english");
        
        logger.info("AopImplementController ==> talk method : {}", chinese.getClass());
        logger.info("AopImplementController ==> talk method : {}", american.getClass());

        return "success";
    }
}

复制代码

5、效果图

问题出现了,按照之前的说法,Chinese应该是用JDK动态代理,American使用CGLIB动态代理才对。


 

  由于博主使用的是Spring Boot 2.0 ,在 Spring Boot 2.0 中,Spring Boot现在默认使用CGLIB动态代理(基于类的动态代理), 包括AOP。 如果需要基于接口的动态代理(JDK基于接口的动态代理) ,需要设置spring.aop.proxy-target-class属性为false。

因此在application.properties加上配置spring.aop.proxy-target-class=false。重启访问

如图:

这样才达到了预期的目的


通过网上查找:

  • 想要强制使用CGLIB,那么就设置<aop:config>下面的proxy-target-class属性为true
<aop:config proxy-target-class="true">
        <!-- other beans defined here... -->
</aop:config>

此处没有验证,仅供参考。

  • 要是使用@AspectJ强制使用CGLIB的话,可以配置<aop:aspectj-autoproxy>下的proxy-target-class属性为true
<aop:aspectj-autoproxy proxy-target-class="true"/>

此处博主用Spring Boot 2.0,加以上配置无效,还是使用默认的CGLIB动态代理。失效的具体原因有待查验!

  • 要是使用@AspectJ强制使用CGLIB的话,向@EnableAspectJAutoProxy注解中添加属性proxyTargetClass = true

此处博主用Spring Boot 2.0,加以上配置无效,还是使用默认的CGLIB动态代理。失效的具体原因有待查验!

二、Spring AOP动态代理实现

1、JDK动态代理

复制代码

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Description: Spring AOP之JDK动态代理实现
 * @ClassName: JDKDynamicSubject 
 * @author OnlyMate
 * @Date 2018年9月11日 下午4:47:53  
 *
 */
public class JDKDynamicObject implements InvocationHandler {
    private Logger logger = LoggerFactory.getLogger(JDKDynamicObject.class);

    private Object target;

    public JDKDynamicObject() {
    }

    /**
     * @Description: 绑定对象,并生成代理对象
     * @Title: bind 
     * @author OnlyMate
     * @Date 2018年9月11日 下午4:48:31 
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        // 取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        logger.info("JDKDynamicObject ==> invoke method : {},{},{}", proxy.getClass(), method.getName(),
                args.toString());
        method.invoke(target, args);
        return null;
    }
}

复制代码

2、CGLIB动态代理

复制代码

import java.lang.reflect.Method;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;

/**
 * @Description: Spring AOP之CGLIB动态代理实现
 * @ClassName: CGLIBDynamicObject 
 * @author OnlyMate
 * @Date 2018年9月11日 下午4:57:30  
 *
 */
public class CGLIBDynamicObject implements InvocationHandler {
    private Logger logger = LoggerFactory.getLogger(CGLIBDynamicObject.class);

    private Object target;

    public CGLIBDynamicObject() {
    }

    /**
     * @Description: 绑定对象,并生成代理对象
     * @Title: bind 
     * @author OnlyMate
     * @Date 2018年9月11日 下午4:48:31 
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        // 回调方法
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }

    
    @Override
    public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
        logger.info("CGLIBDynamicObject ==> invoke method : {},{},{}", arg0.getClass(), arg1.getName(),
                arg2.toString());
        arg1.invoke(target, arg2);
        return null;
    }
}

复制代码

 

3、运行

复制代码

@Controller
@RequestMapping(value="/aop")
public class AopImplementController {
    private Logger logger = LoggerFactory.getLogger(AopImplementController.class);
    
    @Autowired
    private Person chinese;
    @Autowired
    private American american;
    
    @ResponseBody
    @RequestMapping(value="/talk")
    public String talk() {
//        chinese.say("中国人说汉语");
//        american.say("American say english");
//        
//        logger.info("AopImplementController ==> talk method : {}", chinese.getClass());
//        logger.info("AopImplementController ==> talk method : {}", american.getClass());
        
        //自定义JDK动态代理
//        Chinese chinese1 = new Chinese();
//        InvocationHandler dsc = new JDKDynamicObject(chinese1);
//        Person person = (Person) Proxy.newProxyInstance(chinese1.getClass().getClassLoader(), chinese1.getClass().getInterfaces(), dsc);
//        person.say("中国人说汉语");
//        logger.info("AopImplementController ==> talk method : JDKDynamicObject {}", person.getClass());
        
        JDKDynamicObject dsc1 = new JDKDynamicObject();
        Person person1 = (Person)dsc1.bind(new Chinese());
        person1.say("中国人说汉语");
        logger.info("AopImplementController ==> talk method : JDKDynamicObject {}", person1.getClass());
        
        //自定义CGLIB动态代理
        CGLIBDynamicObject dsm = new CGLIBDynamicObject();
        American american1 = (American) dsm.bind(new American()); 
        american1.say("American say english");
        logger.info("AopImplementController ==> talk method : CGLIBDynamicObject {}", american1.getClass());
        return "success";
        
    }

}

复制代码

4、效果图

简单的实现了Spring AOP的JDK动态代理和CGLIB动态代理。

What a meaningless sense if losing myself,though owning all of the world.

本文转载自:https://www.cnblogs.com/onlymate/p/9630788.html

spinachgit
粉丝 10
博文 267
码字总数 272105
作品 0
亳州
架构师
私信 提问
Spring AOP就是这么简单啦

前言 只有光头才能变强 上一篇已经讲解了Spring IOC知识点一网打尽!,这篇主要是讲解Spring的AOP模块~ 之前我已经写过一篇关于AOP的文章了,那篇把比较重要的知识点都讲解过了一篇啦:Sprin...

Java3y
2018/05/24
261
0
为什么要有Spring AOP?

上一篇从Web开发演进过程的一个侧面简述了一下为什么会有Spring?事实上只介绍了为什么会有Spring IOC(控制反转/依赖注入)以及Spring IOC的雏形。我们知道Spring的两个核心知识点是:IOC和...

临江仙卜算子
2018/08/16
49
0
Spring AOP是什么?你都拿它做什么?

对于最近博主最近写博客的兴致大发,我也在思考:为什么而写博客?在互联网时代,无论你是牛人大咖,还是小白菜鸟,都有发表自己看法的权利。无论你是对的还是错的,都会在这个平台上找到答案...

我叫刘半仙
2017/07/18
12.1K
36
spring基础知识---AOP介绍和实现

Spring Boot实践——AOP实现 spring aop两种配置方式 借鉴:http://www.cnblogs.com/xrq730/p/4919025.html    https://blog.csdn.net/zhaokejin521/article/details/50144753     http......

spinachgit
02/21
8
0
Spring AOP是什么?你都拿它做什么?

原文出处:我叫刘半仙 为什么会有面向切面编程(AOP)?我们知道Java是一个面向对象(OOP)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志、...

我叫刘半仙
01/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

一起来学Java8(四)——复合Lambda

在一起来学Java8(二)——Lambda表达式中我们学习了Lambda表达式的基本用法,现在来了解下复合Lambda。 Lambda表达式的的书写离不开函数式接口,复合Lambda的意思是在使用Lambda表达式实现函...

猿敲月下码
35分钟前
9
0
debian10使用putty配置交换机console口

前言:Linux的推广普及,需要配合解决实际应用方能有成效! 最近强迫自己用linux进行实际工作,过程很痛苦,还好通过网络一一解决,感谢各位无私网友博客的帮助! 系统:debian10 桌面:xfc...

W_Lu
今天
10
0
aelf Enterprise 0.8.0 beta有奖公测,“Bug奖金计划”重磅开启

2019年9月30日,aelf Enterprise 0.8.0 beta版正式发布。aelf Enterprise 0.8.0 beta是一个完备的区块链系统, 包含完备的区块链系统、开发套件、开发文档、以及配套的基础应用和基础服务。 ...

AELF开发者社区
今天
10
0
oracle 初始化数据库脚本

create user lpf identified by 123456; create tablespace lpf_ts_cms datafile '/opt/app/oracle/product/11.2.0/lpf.dbf' size 200M; alter user lpf default tablespace lpf_ts_cms; sel......

internetafei
今天
8
0
深入了解Redis底层数据结构

说明 说到Redis的数据结构,我们大概会很快想到Redis的5种常见数据结构:字符串(String)、列表(List)、散列(Hash)、集合(Set)、有序集合(Sorted Set),以及他们的特点和运用场景。不过它们是...

TurboSanil
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部