什么是aop ?
aop(Aspect-Oriented Programming) ,即面向切面编程,面向切面的目的就是,抽象重复代码,复用代码,提高编码效率
aop 实现原理?
Spring aop 底层原理很简单,即为动态代理模式,包含jdk 动态代理和cglb 动态代理
什么是动态代理?
要搞清楚什么是动态代理,那就要搞清楚什么是静态代理模式 ?
代理模式,属于23种设计模式中的结构型模式,copy 网上一句话来简单描述一下:
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介
、、、一脸懵逼
talk is cheap show me the code
静态代理
声明一个 User 类,
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
声明一个Talk 接口,有一个 hello 方法
public interface Talk {
void hello(User user);
}
声明一个UserService 类,实现Talk接口
public class UserService implements Talk{
@Override
public void hello(User user) {
System.out.println("你好,我是: "+user.getName());
}
}
假如现在要实现一个功能,hello 方法执行前后打印日志,静态代理类就这样实现
public class StaticProxy implements Talk{
private UserService userService = new UserService();
@Override
public void hello(User user) {
System.out.println("before");
userService.hello(user);
System.out.println("after");
}
}
写个main 方法来看看结果
public static void main(String[] args) {
User user = new User();
user.setName("静态代理");
StaticProxy staticProxy = new StaticProxy();
staticProxy.hello(user);
}
结果
before
你好,我是: 静态代理
after
分别在hello 方法执行前后打印日志,实现了我们的需求
实现这个需求后,再加入,现在有100个方法,需要在执行前后打印日志,那么这个时候怎么办呢,上面代码写100遍嘛?显然这是不科学的,科学的方法就是使用我们的动态代理,重复的事情交给代码做,由代码来生成代理类,动态代理就是这么回事儿,动态代理实现方式spring aop 采用的主要两种,jdk 动态代理和 cglib 动态代理
jdk 动态代理
下面我们使用jdk 动态代理来实现上述需求
声明一个 MyHandler 类,实现InvocationHandler接口
public class MyHandler implements InvocationHandler {
private Object target;
public MyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(target, args);
System.out.println("after");
return result;
}
public T getProxyInstance(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
}
执行代理类
public static void main(String[] args) {
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
User user = new User();
user.setName("jdk动态代理");
MyHandler handler = new MyHandler(new UserService());
Talk proxyInstance = handler.getProxyInstance();
proxyInstance.hello(user);
}
结果
before
你好,我是: jdk动态代理
after
结果符合预期
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
这一句的作用是,生成代理类字节码到本地,下面我们来看看生成的代理类
public final class $Proxy0 extends Proxy implements Talk {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void hello(User var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.hzins.springboot.admin.pattern.proxy.Talk").getMethod("hello", Class.forName("com.hzins.springboot.admin.pattern.proxy.User"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我们看到$Proxy0这个代理类,继承了Proxy 类,实现了我们定义的Talk接口,看到这儿就想起了一个问题,jdk 动态代理的目标类,有一个必要条件,必须实现一个接口,不然jdk 动态代理机制没有办法为我们生成代理类,这也是为什么spring aop 需要采用jdk 和 cglib 2种方案来实现,因为cglib 是采用继承的方式,而不需要实现接口,(我们可以通配置参数???强制spring 采用cglib)
观察代理类发现,重写了hello方法,m3 通过反射获取目标类的Method实例
super.h.invoke(this, m3, new Object[]{var1})
执行的是MyHandler 中实现的invoke 方法,最终实现了代理
接下来再看看cglib动态代理 是如何实现的
声明一个拦截器,实现MethodInterceptor
public class MyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("after");
return result;
}
}
执行cglib
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
User user = new User();
user.setName("Cglib动态代理");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MyInterceptor());
Talk proxyInstance = (Talk) enhancer.create();
proxyInstance.hello(user);
}
结果
before
你好,我是: Cglib动态代理
after
cglib原理通过ASM框架生成代理类继承于目标类,使用FastClass机制访问方法,相较于反射,性能更好,通过设置下面参数,将代理类生产到D盘
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
可以看到,不同于jdk动态代理,这里生产了3个字节码文件,分别是代理类与两个FastClass类
FastClass机制
FastClass机制就是对一个类的方法建立索引,调用方法时根据方法的签名来计算索引,通过索引来直接调用相应的方法。2个FastClass类分别为目标类和代理类的FastClass
搞清楚了什么是动态代理后,想想我们的100个类方法执行前后打印日志这个需求,是不是就不用写100遍了,只需要通过代码生成100个代理即可,但是想一下我们生成代理类的代码长什么样
Talk proxyInstance = handler.getProxyInstance();
把这个代码写100遍,好像也很痛苦,这可怎么办呀 >.<,并且我们只想为某一些方法进行增强,而不是整个类。那么使用 Spring aop就行 ,它的出现就是为了让程序猿更方便的使用动态代理这一特性,让我们专注于业务代码,curd curd curd ...
Spring aop 的基本概念
- Aspect 切面是Pointcut和Advice的集合,一般单独作为一个类。Pointcut和Advice共同定义了关于切面的全部内容,在什么时候,何处完成何种功能
- Advice 这是在方法执行之前或之后采取的实际操作。 这是在Spring AOP框架的程序执行期间调用的实际代码片段
- JoinPoint 连接点,一般是被拦截的方法
- Pointcut 这是一组一个或多个切入点,在切点应该执行Advice。 可以使用表达式或模式指定切入点
- Introduction 引用允许我们向现有的类添加新的方法或者属性
- Weaving 创建一个被增强对象的过程。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入
Spring aop 的2种使用方式
- 使用 XML 的方式来配置,使用 命名空间
- @AspectJ 配置:注解方式。
Spring aop 注解方式实现日志打印需求
开启AOP
@EnableAspectJAutoProxy
定义切面 Aspect
@Component
@Aspect
public class UserServiceAspect {
}
定义切点 Pointcut ,拦截签名为hello方法
@Pointcut("execution(* hello(..))")
public void pointcut() {}
定义通知(增强)Advice
@Before("com.hzins.springboot.admin.pattern.proxy.UserServiceAspect.pointcut()")
public void doBefore(JoinPoint joinPoint) {
// 前置增强
System.out.println("before");
}
@After("com.hzins.springboot.admin.pattern.proxy.UserServiceAspect.pointcut()")
public void doAfter(JoinPoint joinPoint){
// 后置增强
System.out.println("after");
}
执行单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = AdminServerApplication.class)
public class AopTest {
@Autowired
private UserService userService;
@Test
public void test(){
User user = new User();
user.setName("spring aop");
userService.hello(user);
}
}
结果
before
你好,我是: spring aop
after
通过Pointcut,可以灵活指定需要被代理的方法,Pointcut表达式有多种方式
切点表达式,主要分为匹配方法(execution),匹配注解(@within,@target,@args,@annotation),匹配包/类型(within()),匹配对象(target,this),匹配参数(args)
execution()
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)within() 用于匹配指定的类及其子类中的所有方法
within(declaring-type-pattern)this() 匹配可以向上转型为this指定的类型的代理对象中的所有方法
target() 匹配可以向上转型为target指定的类型的目标对象中的所有方法
args() 用于匹配当前执行的方法传入的参数为指定类型的执行方法
args(param-pattern)@annotation() 匹配带指定注解方法的连接点
@annotation(annotation-type)@within() 匹配指定注解标注的类内的方法都执行
@within(annotation-type)@target 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
@args() 用于匹配运行时 传入的参数列表的类型持有 注解列表对应的注解的方法
@args(annotation-type)
通配符
* 匹配任何数量字符
.. 匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数
+ 匹配指定类型的子类型
操作符
&&或者 and 与操作符
|| 或者 or 或操作符
! 或者 not 非操作符
Advice
执行顺序
多个Aspect切面,advice执行顺序
Spring AOP通过指定aspect的优先级,来控制不同aspect,advice的执行顺序,有两种方式:
Aspect 类添加注解:org.springframework.core.annotation.Order,使用注解value属性指定优先级。
Aspect 类实现接口:org.springframework.core.Ordered,实现 Ordered 接口的 getOrder() 方法。
数值越低,表明优先级越高,@Order 默认为最低优先级,即最大数值
先入后出,后入先出
Weaving
Spring和其他纯Java AOP框架一样,在运行时完成织入,那么我们通过源码来看看这个织入过程
AspectJAwareAdvisorAutoProxyCreator是aop的入口类,它的父类AbstractAutoProxyCreator继承了BeanPostProcessor,所以在目标类注入时后置回调,生成代理类
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 生成代理类
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// Create proxy if we have advice.
//先看它的注释,大意说:如果有通知,就创建代理。
//其实就是在bean.getClass()找到所有的通知和advisor
//这里面其实又分为两个步骤:
//第一,在Bean工厂找到所有的Advisor 第二,根据beanClass和Pointcut去做匹配
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//真正创建代理并返回,这时候返回的就是代理类了,把真实的bean替换掉
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
createProxy 创建代理的过程,大概就是默认使用jdk为有接口的类创建代理,无接口的类使用cglib创建代理,然后返回注入;
仔细阅读源码发现,还有调用wrapIfNecessary 的地方
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
this.earlyProxyReferences.add(cacheKey);
}
return wrapIfNecessary(bean, beanName, cacheKey);
}
getEarlyBeanReference 是SmartInstantiationAwareBeanPostProcessor 接口的方法,这个方法是获取bean提前引用的意思,Spring中解决循环引用的时候有调用这个方法
代理模式,可以说是无处不在,很多框架中都有使用到
比如在catfish中,也使用到了cglib代理来为我们的rpc接口生成代理类 (公司框架)
public class CglibClientProxy implements ClientProxy {
@SuppressWarnings("unchecked")
@Override
public T buildProxy(Class serviceInterface, ClientConfig clientConfig, RemoteClientProcessor remoteClientProcessor) {
Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(new Class[]{serviceInterface});
enhancer.setNamingPolicy(CatfishNamingPolicy.INSTANCE);
enhancer.setCallback(new ClientMethodInterceptor(clientConfig, remoteClientProcessor));
return (T) enhancer.create();
}
}
ScanClientAnnotationPostProcessor 实现 BeanPostProcessor 接口进行代理织入(创建)
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
registerClientBean(beanName, bean);
return bean;
}
... 省略
setTargetProxyBean(bean, field, convertClientConfig(AnnotationUtils.findAnnotation(field, CatfishClient.class)));
... 省略
field.set(bean, clientProxy.buildProxy(field.getType(), clientConfig, remoteClientProcessor));
...