目录
1.5 Spring AOP原理为什么用2种实现方式?JDKProxy和Cglib?
1.8 DefaultAopProxyFactory 与 CglibAopProxy 与 JdkDynamicAopProxy
前言:
提起AOP,那么更多会提到切面注解和设计模式(代理模式)
Spring AOP用的是哪种设计模式?
谈谈你对代理模式的理解?
静态代理和动态代理有什么区别?
如何实现动态代理?
Spring AOP中用的是哪种代理技术?
好吧,这些问题,逐个攻破,毕竟动态代理模式也是必问的东西。
之前写过一个:
[AOP]java自定义登录切面;
https://blog.csdn.net/pmdream/article/details/99983571
不过业务代码写的多了,对于这种切面也都是小case了,很熟悉了。
这边涉及到了用户权限/权限的问题,后续再补。
这篇就纯写对AOP的理解
1. AOP
在运行时,动态的将代码切入到类的制定方法,指定位置上面的思想就是面向切面编程。
比如在我业务中,有用到在controller 切面log,切面登录和权限
在service中也有切面动态数据源。
它可以对整个类进行切面,也可以对指定的某个接口进行切面。
AOP就是面向切面编程,是OOP(面向对象)的编程。
其实AOP也是在解耦代码,对于重复而复杂得东西提出来,只用一个注解就可以注入进去了。
与系统相关的东西,比如权限,日志,事务。
可以独立编码然后AOP注入到系统里面。
1.1 AOP相关概念
(1)Aspect :切面,切入系统的一个切面。
(2)Join point :连接点,也就是可以进行横向切入的位置;
(3) Advice :通知,切面在某个连接点执行的操作(分为: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );
(4)Pointcut :切点,符合切点表达式的连接点,也就是真正被切入的地方;
1.2 静态AOP与动态AOP
静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。
动态AOP是指将切面代码进行动态织入实现的AOP。
Spring的AOP为动态AOP,实现的技术为: JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术) 。尽管实现技术不一样,但 都是基于代理模式 , 都是生成一个代理对象 。
1.3 JDK的动态代理?
package com.dk.learndemo.aop.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author :pmdream
* @description : MyJDKProxy
* 要求被代理的类必须实现一个接口,核心是InvocationHandler接口和Proxy类
* @create : 2020/03/18
*/
public class MyJDKProxy implements InvocationHandler {
//需要被代理的目标对象
private Object proxyObject;
public MyJDKProxy() {
}
public MyJDKProxy(Object proxyObject) {
this.proxyObject = proxyObject;
}
/**
*
* 通过invoke来进行的动态代理
* 三个参数分别是代理实例、调用的方法、方法的参数列表。
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理,监听开始!");
Object result = method.invoke(proxyObject, args);
System.out.println("JDK动态代理,监听结束!");
return result;
}
/**
*
* 1. JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
* 第二个参数 是我们提供给代理对象的接口,那么我们这个代理对象就会去实现这个接口,
* 这种情况下,我们当然可以将这个代理对象强制转化成提供的接口类型。
* 2. 这边的This指代proxyObject,可以用myJDKProxy来代替
* 3. Proxy.newProxyInstance创建的代理对象是在JVM运行时动态生成的一个对象,这个对象不是我们知道的任何一个对象,
* 而是运行时动态生成的,并且命名方式都是以$Proxy这种类型的。看运行结果也看出来 $Proxy0就是实际代理类。
* 所以才是动态代理。
*
* */
private Object getProxyInstance(Object targetObject) {
//为目标对象target赋值
this.proxyObject = targetObject;
MyJDKProxy myJDKProxy = new MyJDKProxy(targetObject);
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader()
, targetObject.getClass().getInterfaces(), this);
}
public static void main(String[] args) {
//实例化JDKProxy对象
MyJDKProxy myJDKProxy = new MyJDKProxy();
//获取代理对象,UserServiceImpl倒不如说是代理的最终生成的目标,动态代理会获取这个类所实现的接口,然后创建一个动态的类来实现这个接口
UserService userService = (UserService)myJDKProxy.getProxyInstance(new UserServiceImpl());
System.out.println("下面的方法才是调用的方法,应该通过invoke来实现的,所以上面的userService是相当于一个实现了UserService接口的代理类");
//代理类调用方法会用到invoke
userService.delUser("hehe");
System.out.println(userService.getClass());
}
}
package com.dk.learndemo.aop.jdk;
/**
* @author :pmdream
* @description : UserService
* JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口,核心是InvocationHandler接口和Proxy类
* @create : 2020/03/18
*/
public interface UserService {
void addUser(String userName,String password);
String delUser(String userName);
}
package com.dk.learndemo.aop.jdk;
/**
* @author :pmdream
* @description : UserServiceImpl
* @create : 2020/03/18
*/
public class UserServiceImpl implements UserService {
//重写新增用户方法
@Override
public void addUser(String userName, String password) {
System.out.println("调用了新增的方法!");
System.out.println("传入参数为 userName: "+userName+" password: "+password);
}
//重写删除用户方法
@Override
public String delUser(String userName) {
System.out.println("调用了删除的方法!");
System.out.println("传入参数为 userName: "+userName);
return "删除成功";
}
}
1.4 基于Cglib的动态代理?
package com.dk.learndemo.aop.cglib2;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author :zhudakang
* @description : CglibProxy
* @create : 2020/03/18
*/
public class CglibProxy implements MethodInterceptor {
private Object target;
private Class clazz;
/**
* o 为代理的最终目标
* clazz 是代理的目标的类型
* */
public CglibProxy(Object o, Class clazz) {
this.target = o;
this.clazz = clazz;
}
public Object getNewProxy() {
Enhancer enhancer = new Enhancer();
//要动态生成一个子类
enhancer.setSuperclass(clazz);
//通过回调指定代理类。
enhancer.setCallback(CglibProxy.this);
return enhancer.create();
}
/**
* intercept [ˌɪntərˈsept] 拦截
* */
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("intercept方法invoke前");
Object e = method.invoke(target, objects);
System.out.println("intercept方法invoke后");
return e;
}
public static void main(String[] args) {
Work work = new Work();
CglibProxy cglibProxy = new CglibProxy(work, Work.class);
Work workProxy = (Work) cglibProxy.getNewProxy();
workProxy.goToWork();
workProxy.eatLunch();
workProxy.getOffWork();
//我们可以通过class来判断AOP是使用的哪种方式的代理。
//class com.dk.learndemo.aop.cglib2.Work$$EnhancerByCGLIB$$b3dafa56
System.out.println(workProxy.getClass());
}
}
package com.dk.learndemo.aop.cglib2;
/**
* @author :zhudakang
* @description : DayWork
* @create : 2020/03/18
*/
public class Work {
public void goToWork() {
System.out.println("上班");
}
public void eatLunch() {
System.out.println("吃午饭");
}
public void getOffWork() {
System.out.println("下班");
}
}
1.5 Spring AOP原理为什么用2种实现方式?JDKProxy和Cglib?
当目标类为接口时用JDKProxy,否则用Cglib。为什么不直接用一种技术?
JDK动态代理只能代理接口类,所以很多人设计架构的时候会使用
XxxService, XxxServiceImpl的形式设计,一是让接口和实现分离,二是也有助于代理。
为什么不都使用Cgilb代理:?
因为JDK动态代理不依赖其他包,Cglib需要导入ASM包,对于简单的有接口的代理使用JDK动态代理可以少导入一个包。
JDKProxy只能代理接口,这种局限性导致了必须要引入Cglib
Jdk代理:基于接口的代理,一定是基于接口,会生成目标对象的接口的子对象。
Cglib代理:基于类的代理,不需要基于接口,会生成目标对象的子对象。
jdk代理对象实质上是目标对象接口的实现类
Cglib代理对象实质上是目标对象的子类
JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理。这个时候就需要使用Cglib在字节码上做代理。
其实还是JDK本身的局限性导致的。
首先并不是目标类为接口时使用JDK 反之用Cglib,确切的表述应该,如果代理类的方法不是实现接口的方法,那必须使用Cglib,否之两者都可以。
因为JDK动态代理的原理就是实现和目标对象同样的接口, 因此只能调用那些接口方法。Cglib则没有此限制,因为它所创建出来的代理对象就是目标类的子类,因此可以调用目标类的任何方法。
撸完代码之后,对于这两种其实就极为清晰了。
但是对于JDK7和JDK8,使用JDK代理并不比cglib慢多少了,已经完全追上。
1.6 spring aop 注意事项
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 如果目标对象实现了接口,也可以强制使用CGLIB实现AOP 如果目标对象没有实现接口,必须采用CGLIB的动态代理,spring会自动在两种模式之间转换 不管哪一种方式都不能用private和final做修饰词
1.7 spring在做AOP时候选择哪种?
(1)当Bean实现接口时,Spring就会用JDK的动态代理
(2)当Bean没有实现接口时,Spring使用CGlib是实现
(3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)
1.8 DefaultAopProxyFactory 与 CglibAopProxy 与 JdkDynamicAopProxy
他们的包都是org.springframework.aop.framework;
public class CglibAopProxy implements AopProxy, Serializable {}
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {}
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {}
CglibAopProxy 和jdkDynamicAopProxy 实现了AopProxy的接口。
AOP中对这两种代理的支持都是从ProxyFactoryBean
中的getObject
方法开始。
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}
最终是通过 调用ProxyCreatorSupport
这个类(实现AopProxyFactory
接口)中的createAopProxy
来创建代理类。
1.9 AOP如何判断使用哪种代理方式(源码)
最终的createAopProxy
实现类就是上面的DefaultAopProxyFactory
来实现的。所以最终决定调用那种代理方式的依据是在DefaultAopProxyFactory
这个类的createAopProxy
方法中
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.aop.framework;
import java.io.Serializable;
import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
public DefaultAopProxyFactory() {
}
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config);
} else {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
} else {
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return ifcs.length == 0 || ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]);
}
}
参考:
https://blog.csdn.net/weixin_38362455/article/details/91055939
https://blog.csdn.net/bicheng4769/article/details/80030266
https://www.cnblogs.com/bigmonkeys/p/7823268.html