SpingAop
面向切面编程:相对于OOP面向对象编程,Spring的AOP的存在目的是为了解耦。AOP可以让一组类共享相同的行为。在OOP中只能继承和实现接口,且类继承只能单继承,阻碍更多行为添加到一组类上,AOP弥补了OOP的不足,让业务逻辑关注业务本身,不用去关心其它的事情,比如事务。有如下优点:
- 集中的处理某一关注点/横切逻辑
- 可以很方便的添加/删除 关注点
- 侵入性少,增强代码的可读性和可维护性
业务场景
- 事务控制:给目标方法添加前后的声明周期,事务开启、回滚、提交等
- 日志记录:给目标方法添加日志记录,记录方法的执行时间、接收参数、返回结果等
- 权限控制:给目标方法添加权限控制,在方法执行前,判断是否有执行该方法的权限
- 异常处理:对于目标方法的返回异常进行处理,给出友善的错误提示
- 国际化:对于返回结果进行处理,如中英文替换等
- …
常用术语
-
通知(Advice)
有的地方又叫做增强,需要完成的工作叫做通知,入事务控制、日志记录等,你要先定义好,等到使用的时候再去用
-
切点(Poingcut)
一个类中的所有方法都可以是切点,根据需要筛选出需要添加通知的方法,切点是为了找到需要增强的方法
最常用的切点:
execution(* com.jiuxian..service.*.*(..))
- execution 表达式的主体
- 第一个* 代表任意的返回值
- com.jiuxian aop所横切的包名
- 包后面… 表示当前包及其子包
- 第二个* 表示类名,代表所有类
- .*(…) 表示任何方法,括号代表参数 … 表示任意参数
-
切面(Aspect)
其实就是切点和通知的结合,通知和切点定义了切面的全部内容,它是干什么的,什么时候在哪执行
-
织入(Weaving)
把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:
- 编译期: 切面在目标类编译时被织入,这种方式需要特殊的编译器
- 类加载期: 切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码
- 运行期: 切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的。
AopDemo
- 添加spring-aop依赖
<!-- aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 定义一个切面
package com.nubo.jfs.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
1. @description 日志拦截
2. @author liubin
3. @date 20/4/21 11:45
*/
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(public * com.nubo.jfs.service.*.*(..))")
public void pointcut() {
}
@Around("pointcut()")
public void around(ProceedingJoinPoint point) {
long t1 = System.currentTimeMillis();
System.out.println("日志记录开始");
try {
point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long t2 = System.currentTimeMillis();
System.out.println("日志记录结束,耗时:" + (t2 - t1) + "ms");
}
}
- 五种通知(增强)代码实现
@Before("pointcut()")
public void beginTransaction() {
System.out.println("before beginTransaction");
}
@After("pointcut()")
public void commit() {
System.out.println("after commit");
}
@AfterReturning(pointcut = "pointcut()", returning = "returnObject")
public void afterReturning(JoinPoint joinPoint, Object returnObject) {
System.out.println("afterReturning");
}
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("afterThrowing afterThrowing rollback");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
System.out.println("around");
return joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
throw e;
} finally {
System.out.println("around");
}
}
- 注解的方式注入
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auth {
boolean hasAuth() default true;
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
- @description 权限拦截
- @author liubin
- @date 20/4/21 15:28
*/
@Aspect
@Component
@Order(1)
public class AuthAspect {
@Pointcut(value = "@annotation(auth)", argNames = "auth")
public void pointcut(Auth auth) {
}
@Around(value = "pointcut(auth)", argNames = "joinPoint,auth")
public void aroung(ProceedingJoinPoint joinPoint, Auth auth) {
if (auth.hasAuth()) {
System.out.println("have auth...");
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
} else {
System.out.println("not have auth...");
}
}
}
AOP底层原理
在spring对实例进行初始化的时候,最后会调用
- AbstractAutowireCapableBeanFactory.createBean() 创建bean
- AbstractAutowireCapableBeanFactory.initializeBean() 初始化bean对象
- AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization() 注入后续属性
- AbstractAutoProxyCreator.postProcessAfterInitialization() 织入Aop通知
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
// 如果配置了属性spring.aop.proxy-target-class = true,则强制使用cglib代理
if (!proxyFactory.isProxyTargetClass()) {
// 是否需要代理类或者代理接口
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {// 如果代理的是接口,则判断是否实现了接口,没有实现接口使用代理类
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
}
根据代理类型获取代理方式,代理类使用cgilb动态代理,代理接口使用jdk动态代理
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// isOptimize默认false || 是代理类? || 没继承接口?
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
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.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
/**
* Determine whether the supplied {@link AdvisedSupport} has only the
* {@link org.springframework.aop.SpringProxy} interface specified
* (or no proxy interfaces specified at all).
*/
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
}
从上源码可以看到,SpringAop的底层实现是通过jdk动态代理和Cglib代理来实现的,也可以通过参数控制使用哪种代理模式
spring.aop.proxy-target-class = true/false
spring默认使用jdk动态代理,而springboot默认使用的cglib动态代理
spingboot官方是这样解释的:他们认为使用cglib更不容易出现转换错误
This was changed in 1.4 (see 5423). We’ve generally found cglib proxies less likely to cause unexpected cast exceptions.
SpringBoot 默认的配置文件中是这样显示的 spring-configuration-metadata.json
{
"name": "spring.aop.auto",
"type": "java.lang.Boolean",
"description": "Add @EnableAspectJAutoProxy.",
"defaultValue": true
},
{
"name": "spring.aop.proxy-target-class",
"type": "java.lang.Boolean",
"description": "Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).",
"defaultValue": true
}
我们发现 SpringBoot 中的注解@EnableAspectJAutoProxy 或者@EnableTransactionManagement 默认的proxyTargetClass 就是false,说明这上面的设置并未生效,
@EnableAspectJAutoProxy(proxyTargetClass = false)
@EnableTransactionManagement(proxyTargetClass = false)
如果想要走jdk动态代理,还需要在配置文件设置设置
spring.aop.proxy-target-class = false
以下是走哪个代理方式的配置方式
配置spring.aop.auto | 配置 spring.aop.proxy-target-class | 注解的proxyTargetClass | 代理 方式 |
---|---|---|---|
true | true | true | Cglib |
true | false | true | Cglib |
true | true | false | Cglib |
true | false | false | JDK动态代理 |
false | true | true | Cglib |
false | false | true | Cglib |
false | true | false | JDK动态代理 |
false | false | false | JDK动态代理 |
代理模式
静态代理
由程序员创建类再对其进行编译,在程序运行前代理类的的.class就已经存在了
- 继承代理
公共的取放的接口类
/**
* @description 取放的接口
* @author liubin
* @date 20/4/21 09:12
*/
public interface TakeService {
void takeIn();
void takeOut();
}
取药拿药的操作类
/**
* @description 拿药
* @author liubin
* @date 20/4/21 09:22
*/
@Service
public class DrugServiceImpl implements TakeService {
@Override
public void takeIn() {
// 补药
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("take in drug...");
}
@Override
public void takeOut() {
// 拿药
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("take out drug...");
}
}
对取药拿药的操作织入日志记录的功能
/**
* @description 日志记录
* @author liubin
* @date 20/4/21 09:33
*/
public class ExtendDrugLogProxy extends DrugServiceImpl {
@Override
public void takeIn() {
long t1 = System.currentTimeMillis();
System.out.println("日志记录开始");
super.takeIn();
long t2 = System.currentTimeMillis();
System.out.println("日志记录结束,耗时:" + (t2 - t1) + "ms");
}
}
- 聚合代理
使用聚合代理的方式对拿药取药的操作织入日志记录
/**
- @description 日志记录
- @author liubin
- @date 20/4/21 09:50
*/
public class ImplementDrugLogProxy implements TakeService {
private TakeService takeService;
public ImplementDrugLogProxy(TakeService takeService) {
this.takeService = takeService;
}
@Override
public void takeIn() {
long t1 = System.currentTimeMillis();
System.out.println("日志记录开始");
takeService.takeIn();
long t2 = System.currentTimeMillis();
System.out.println("日志记录结束,耗时:" + (t2 - t1) + "ms");
}
@Override
public void takeOut() {
long t1 = System.currentTimeMillis();
System.out.println("日志记录开始");
takeService.takeOut();
long t2 = System.currentTimeMillis();
System.out.println("日志记录结束,耗时:" + (t2 - t1) + "ms");
}
}
-
总结
-
继承方式:每多一个切点,多写一个通知;通知顺序改变,代理类需要重构
-
聚合方式:每多一个切点,多写一个通知;通知顺序改变,只需在调用时改变顺序
动态代理
与静态代理相反,动态代理是在程序编译或者运行的的时候才生成代理类,更加灵活和轻便
1. jdk动态代理
jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用业务方法前调用InvocationHandler处理。代理类必须实现InvocationHandler接口,并且,JDK动态代理只能代理实现了接口的类,没有实现接口的类是不能实现JDK动态代理。结合下面代码来看就比较清晰了。
/**
* @description jdk动态代理
* @author liubin
* @date 20/4/21 10:19
*/
public class JdkLogProxy implements InvocationHandler {
//被代理的对象
private Object target;
public JdkLogProxy(Object target) {
this.target = target;
}
/**
* @param proxy 代理对象
* @param method 增强的方法
* @param args 方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long t1 = System.currentTimeMillis();
System.out.println("日志记录开始");
Object result = method.invoke(target, args);
long t2 = System.currentTimeMillis();
System.out.println("日志记录结束,耗时:" + (t2 - t1) + "ms");
return result;
}
}
2. Cglib动态代理
cglib是针对类来实现代理的,它会对目标类产生一个代理子类,通过方法拦截技术对过滤父类的方法调用。代理子类需要实现MethodInterceptor接口。
/**
* @description cglib动态代理
* @author liubin
* @date 20/4/21 11:11
*/
public class CglibLogProxy implements MethodInterceptor {
/**
* @param object 需要增强的对象
* @param method 表示拦截的方法
* @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
* @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
* @return java.lang.Object
*/
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long t1 = System.currentTimeMillis();
System.out.println("日志记录开始");
Object result = methodProxy.invokeSuper(object, objects);
long t2 = System.currentTimeMillis();
System.out.println("日志记录结束,耗时:" + (t2 - t1) + "ms");
return result;
}
}
区别 | jdk动态代理 | Cglib动态代理 |
---|---|---|
实现原理 | 使用聚合的方式 | 使用继承的方式 |
不能代理的情况 | 没有实现接口类 | final类型的类或方法 |
在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。