spring中的动态代理

一.背景介绍

之前介绍了动态代理的两种方式
jdk动态代理:https://blog.csdn.net/qq_24516549/article/details/89085881
cglib动态代理:https://blog.csdn.net/qq_24516549/article/details/89167591

现在开始研究在spring中的实现有什么不同

二.示例代码

1.注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.METHOD,ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyTest {

}
2.切面类
import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

@Aspect
@Configuration
public class MyTestAspect {

	Logger log = LoggerFactory.getLogger(MyTestAspect.class);

	@Pointcut("@annotation(cn.test.modules.aop.aoptest.annotation.MyTest)")
	public void pointCut() {
	}

	@Before("pointCut()")
	public void doBefore(JoinPoint point) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
		log.info("before:" + getMethod(point).getName());
	}

	private Method getMethod(JoinPoint point) throws NoSuchMethodException, SecurityException {
		MethodSignature methodSignature = (MethodSignature) point.getSignature();
		Class<?> targetClass = point.getTarget().getClass();
		return targetClass.getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
	}
}
3.service
import org.springframework.stereotype.Service;

import cn.test.modules.aop.aoptest.annotation.MyTest;

@Service
public class AService {

	@MyTest
	public void test() {
	}

	@MyTest
	public void hello() {
		test();
	}

}
4.controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cn.test.modules.aop.aoptest.classService.AService;
import io.swagger.annotations.ApiOperation;
import cn.test.common.utils.R;

/**
 * 
 *
 * @author zhanchong
 * @email [email protected]
 * @date 2019-04-10 14:24:36
 */
@RestController
@RequestMapping("aop/test")
public class TestController {
	@Autowired
	private AService a;

	/**
	 * 列表
	 */
	@RequestMapping("/test")
	@ApiOperation(value = "测试aop", httpMethod = "GET")
	public R list(int type) {
		switch (type) {
		case 1:
			a.hello();
			break;
		case 2:
			a.test();
			break;
		case 3:
			AService A = new AService();
			A.hello();
			break;
		default:
			break;
		}
		return R.ok();
	}

}

三.代码调用示例

以上注解和切面类期望达到调用注上MyTest注解的方法之前打印一行log,依次调用123三种情况查看结果

1.demo1

1

输出:

2019-04-11 15:18:52.660  INFO 13244 --- [io-8080-exec-25] c.t.m.aop.aoptest.aspect.MyTestAspect    : before:hello

只输出了hello()方法上的日志,可见自调用的test()方法未被增强

2

输出:

2019-04-11 15:20:10.338  INFO 13244 --- [io-8080-exec-24] c.t.m.aop.aoptest.aspect.MyTestAspect    : before:test

正常输出了test()方法上的日志

3

无输出,未发生任何增强

2.demo2

修改service代码如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Service;

import cn.test.modules.aop.aoptest.annotation.MyTest;

@Service
@EnableAspectJAutoProxy(exposeProxy = true)
public class AService {

	Logger log = LoggerFactory.getLogger(AService.class);

	@MyTest
	public void test() {
		Object o = AopContext.currentProxy();
		log.info("test start");
		log.info(AopUtils.isAopProxy(o) + "");
		log.info(AopUtils.isJdkDynamicProxy(o) + "");
		log.info(AopUtils.isCglibProxy(o) + "");
		log.info("test end");
	}

	@MyTest
	public void hello() {
		Object o = AopContext.currentProxy();
		log.info("hello start");
		log.info(AopUtils.isAopProxy(o) + "");
		log.info(AopUtils.isJdkDynamicProxy(o) + "");
		log.info(AopUtils.isCglibProxy(o) + "");
		log.info("hello end");
		test();
	}

}

依次调用123三种情况查看结果

1

输出:

2019-04-11 15:26:46.620  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.aspect.MyTestAspect    : before:hello
2019-04-11 15:26:46.622  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : hello start
2019-04-11 15:26:46.622  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : true
2019-04-11 15:26:46.622  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : false
2019-04-11 15:26:46.622  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : true
2019-04-11 15:26:46.622  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : hello end
2019-04-11 15:26:46.622  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : test start
2019-04-11 15:26:46.624  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : true
2019-04-11 15:26:46.624  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : false
2019-04-11 15:26:46.624  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : true
2019-04-11 15:26:46.624  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : test end

可见hello被增强,test未被增强;
当前调用的对象是被spring管理的动态代理对象,动态代理方式是cglib

2

输出:

2019-04-11 15:20:10.338  INFO 13244 --- [io-8080-exec-24] c.t.m.aop.aoptest.aspect.MyTestAspect    : before:test2019-04-11 15:27:06.379  INFO 13244 --- [nio-8080-exec-6] c.t.m.aop.aoptest.aspect.MyTestAspect    : before:test
2019-04-11 15:27:06.380  INFO 13244 --- [nio-8080-exec-6] c.t.m.aop.aoptest.classService.AService  : test start
2019-04-11 15:27:06.380  INFO 13244 --- [nio-8080-exec-6] c.t.m.aop.aoptest.classService.AService  : true
2019-04-11 15:27:06.380  INFO 13244 --- [nio-8080-exec-6] c.t.m.aop.aoptest.classService.AService  : false
2019-04-11 15:27:06.380  INFO 13244 --- [nio-8080-exec-6] c.t.m.aop.aoptest.classService.AService  : true
2019-04-11 15:27:06.380  INFO 13244 --- [nio-8080-exec-6] c.t.m.aop.aoptest.classService.AService  : test end

可见test被增强
当前调用的对象是被spring管理的动态代理对象,动态代理方式是cglib

3
2019-04-11 15:27:33.504 ERROR 13244 --- [nio-8080-exec-7] c.t.common.exception.RRExceptionHandler  : Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

报错,调用自定义对象并非被spring管理的对象

3.demo3

修改service代码如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Service;

import cn.test.modules.aop.aoptest.annotation.MyTest;

@Service
@EnableAspectJAutoProxy(exposeProxy = true)
public class AService {

	Logger log = LoggerFactory.getLogger(AService.class);

	@MyTest
	public void test() {
		log.info("test start");
		log.info(AopContext.currentProxy().getClass().getName());
		log.info("test end");
	}

	@MyTest
	public void hello() {
		log.info("hello start");
		log.info(AopContext.currentProxy().getClass().getName());
		log.info("hello end");
		((AService) AopContext.currentProxy()).test();
	}

}

查看1的结果

1

输出:

2019-04-11 15:33:31.397  INFO 13244 --- [nio-8080-exec-1] c.t.m.aop.aoptest.aspect.MyTestAspect    : before:hello
2019-04-11 15:33:31.400  INFO 13244 --- [nio-8080-exec-1] c.t.m.aop.aoptest.classService.AService  : hello start
2019-04-11 15:33:31.400  INFO 13244 --- [nio-8080-exec-1] c.t.m.aop.aoptest.classService.AService  : cn.test.modules.aop.aoptest.classService.AService$$EnhancerBySpringCGLIB$$2641a8af
2019-04-11 15:33:31.400  INFO 13244 --- [nio-8080-exec-1] c.t.m.aop.aoptest.classService.AService  : hello end
2019-04-11 15:33:31.400  INFO 13244 --- [nio-8080-exec-1] c.t.m.aop.aoptest.aspect.MyTestAspect    : before:test
2019-04-11 15:33:31.400  INFO 13244 --- [nio-8080-exec-1] c.t.m.aop.aoptest.classService.AService  : test start
2019-04-11 15:33:31.400  INFO 13244 --- [nio-8080-exec-1] c.t.m.aop.aoptest.classService.AService  : cn.test.modules.aop.aoptest.classService.AService$$EnhancerBySpringCGLIB$$2641a8af
2019-04-11 15:33:31.401  INFO 13244 --- [nio-8080-exec-1] c.t.m.aop.aoptest.classService.AService  : test end

可见hello和test均被增强;
当前调用的对象属于cn.test.modules.aop.aoptest.classService.AService$$EnhancerBySpringCGLIB$$2641a8af

4.demo4

修改service代码如下:

	@MyTest
	public void test() {
		log.info("test start");
		log.info("test end");
	}

	@MyTest
	public void hello() {
		log.info("hello start");
		log.info(AopContext.currentProxy().getClass().getName());
		log.info("hello end");
		new AService().test();
	}

查看1的结果

1

输出:

2019-04-11 15:37:08.917  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.aspect.MyTestAspect    : before:hello
2019-04-11 15:37:08.917  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : hello start
2019-04-11 15:37:08.917  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : cn.test.modules.aop.aoptest.classService.AService$$EnhancerBySpringCGLIB$$2641a8af
2019-04-11 15:37:08.917  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : hello end
2019-04-11 15:37:08.917  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : test start
2019-04-11 15:37:08.917  INFO 13244 --- [nio-8080-exec-3] c.t.m.aop.aoptest.classService.AService  : test end

可见hello被增强,test未被增强;
当前调用的对象属于cn.test.modules.aop.aoptest.classService.AService$$EnhancerBySpringCGLIB$$2641a8af

四.代码调用分析

由上可见,spring在这里采用cglib来管理被动态代理的对象,那么我们之前有看到cglib生成的代理类自调用是会被增强的,为什么spring的cglib代理对象自调用增强不起作用呢?
这是因为spring实现的cglib是cglib风格的代理,实际上还是用类似jdk动态代理中委托的形式来实现的…

关于此我看到如下解释:
The behavior of the Cglib-proxies has nothing to do with the way of how cglib works but with how cglib is used by Spring. Cglib is capable of either delegating or subclassing a call. Have a look at Spring’s DynamicAdvisedInterceptor which implements this delegation. Using the MethodProxy, it could instead perform a super method call.
Spring defines delegation rather than subclassing in order to minimize the difference between using Cglib or Java proxies.

根据以上内容及分析,我们应该可以看出来,spring容器中ioc管理的对象默认是原生对象,但只要该类通过aop增强,该对象就是代理对象,我们用代理对象来调用方法才会触发各种代理增强

关于spring如何选择代理方式,之前可能有看到过优先使用jdk动态代理,没有接口再使用cglib动态代理;
实际上现在任何情况下都会优先选择cglib动态代理,选择jdk动态代理必须手动指定才行了

五.spring的ioc和di

从上面的示例中我们可以看到,直接用autowired注入的对象是被aop增强的我们期望得到的对象,而自己new出来的则只是原始对象

如果我们想要获得被代理的对象,不通过spring的话,需要手动调用代理控制类去增强原始对象,AOP在spring里用的不要太多,可想而知会有多麻烦

而spring就改变了这一情况,他把控制反转了,之前是程序主动去创建依赖对象,而spring则是有一个容器来创建这些对象;所谓控制反转就是ioc容器控制了对这些对象的依赖获取

那么为什么叫反转呢,之前的情况是我们自己在程序中直接获取依赖的对象,这是正转;而现在是容器来创建和注入依赖对象,那么需要依赖对象的实例对依赖对象的依赖也就被反转了

下面参考开涛大神的讲解:

假设A类中有个方法的调用需要B类中的某个方法,而B类又依赖C类
我们此时需要调用A类中的方法,之前需要先创建B对象,再创建C对象;再把C对象注入到B对象里,再调用B的方法
现在,使用spring,我们只需在A类中注入B对象,关于B的依赖Spring会自动帮我们管理,这就是spring的依赖注入

所以说spring实现了依赖注入和控制反转,也实现了aop;

如果你不用依赖注入,那么也就不能方便的使用spring中的aop,而是需要很麻烦的手动设置原始对象的动态代理

下面参考bromon大神的讲解,讲的非常清晰明了:
https://blog.csdn.net/bromon/article/details/326250#comments

Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

你可能感兴趣的:(Java)