《Spring AOP面向切面编程》(上)

1.AOP概览

AOP(Aspect Oriented Programming),什么是面向切面编程?
1)AOP是一种编程范式,不是编程语言

  • 编程范式概览
  • 面向过程编程
  • 面向对象编程
  • 函数式编程
  • 事件驱动编程
  • 面向切面编程

2)AOP是为了解决特定问题,不是解决所有问题。AOP是OOP的补充,不是替代关系。
AOP的初衷是关注点隔离
切面隔离:将功能性需求和非功能性需求分离开来

在这里插入图片描述

做系统设计时,通常将大系统做分解,按业务功能分解成一个个低耦合、高内聚的模块。
分解后发现有趣的事情是,有些东西是公用的,或者跨越了多个模块。

  • 日志:对特定的操作输出日志来记录
  • 安全:在操作之前进行操作检查
  • 性能:统计每个方法的执行时间
  • 事务:方法开始前开始事务,方法结束后提交或回滚事务

我们可以将这些通用模块写好,在实现业务模块的时候去调用就好了。
在这里插入图片描述

但是,功能是实现了,但是业务代码中大部分都是日志、性能、事务等,几乎把真正的业务代码给淹没了!
最好的方法是将日志、安全、事务这样的代码和业务代码完全隔离开来,因为他们的关注点和业务代码的关注点完全不同。
他们之间的关系应该是正交的。如果把这个业务功能看成一层层面包的话, 这些日志/安全/事务 像不像一个个“切面”(Aspect) ?
如果我们能让这些“切面“能和业务独立, 并且能够非常灵活的“织入”到业务方法中, 那就实现了面向切面编程(AOP)!
在这里插入图片描述

AOP的应用场景

  • 审计日志:对特定的操作输出日志来记录
  • 权限控制:在操作之前进行操作检查
  • 性能监控:统计每个方法的执行时间
  • 事务控制:方法开始前开始事务,方法结束后提交或回滚事务
  • 缓存控制
  • 异常处理
    根据织入时机的不同,AOP可以分为以下3种实现方式
    1)编译期织入(AspectJ)
    2)类加载时织入(AspectJ 5+)
    3)运行时织入(Spring AOP)

2.Spring AOP的使用举例

Spring AOP实现权限控制:只有管理员可以进行修改操作

@Service
public class ProductService {
    
    public Product get(Long id) {
        System.out.println("get product id = " + id);
        return new Product(id, "aaa");
    }
    
    @AdminOnly
    public void insert(Product product) {
        System.out.println("insert product");
    }
​
    @AdminOnly
    public void delete(Long id) {
        System.out.println("delete product id = " + id);
    }
}

@Aspect  //1)表明这个是一个切面类
@Component
public class SecurityAspect {
    //2)切入点
    @Pointcut("@annotation(AdminOnly)")
    public void adminOnly() {
    }
    //3)通知
    @Before("adminOnly()")
    public void check() {
        String user = CurrentUserHolder.get();
        if (!"admin".equals(user)) {
            throw new RuntimeException("operation not allow");
        }
    }
}

PointCut切入点

PointCut(切入点):一个方法或一组方法(可以通过通配符支持)。
比如,“对于com.xxx这个包中所有类的execute方法” 。

Advice通知

Advice(通知):方法调用时需要做什么。比如,”在方法调用之前/之后 , 需要执行xxx操作“ 。
具体的通知类型包括:

  • before:前置通知,在方法调用前执行。
  • after returning:方法返回通知,在方法正常返回时执行。
  • throwing:方法异常通知,在方法抛出异常时执行。
  • after:方法结束通知,包含了方法正常返回和方法抛出异常两种情况。
  • around:环绕通知。

3.Spring AOP的实现原理

3.1运行时织入

根据织入时机的不同,AOP可以分为以下3种实现方式

1)编译期织入(AspectJ)
2)类加载时织入(AspectJ 5+)
3)运行时织入(Spring AOP)

Spring中的AOP实现是在运行时织入的。
运行时织入是如何实现的呢?通过动态代理的方式。

3.1.1代理模式

什么是代理模式
在这里插入图片描述
调用方Caller通过Proxy代理对象间接地与目标对象Target交互。
类图
在这里插入图片描述

两个关键点:
1)Client客户端通过接口引用操作的对象Subject。且RealSubject真实对象和Proxy代理对象实现了同一个接口
2)Proxy代理对象会持有RealSubject真实对象的引用,真正要执行的方法委托给真实对象来执行,自己则执行一些额外的逻辑

public interface Subject {
    void request() throws Exception;
}

public class RealSubject implements Subject {

    public void request() {
        System.out.println("RealSubject execute request()");
    }
}

public class Proxy implements Subject {

    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }
    /**
    * 在真实对象request()方法之前或之后,可以执行一些额外的操作
    */
    public void request() throws Exception{
        System.out.println("before");
        try {
            realSubject.request();
        } catch (Exception e) {
            System.out.println("ex:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("after");
        }
    }
}

public class Client {

    public static void main(String[] args) throws Exception{
        Subject subject = new Proxy(new RealSubject());
        subject.request();
    }
}

运行结果

before
RealSubject execute request()
after

上面代码展示的是静态代理的实现。

静态代理有如下缺点:
1)假设真实对象有100方法,则代理对象需要对100个方法进行委托。
2)且在这100个方法中代理对象执行的额外逻辑是一样的。
就像下面代码展示的那样。

public interface Subject {
    void request() throws Exception;
    void request2() throws Exception;
    void request3() throws Exception;
    void request4() throws Exception;

public class RealSubject implements Subject {

    public void request() {
        System.out.println("RealSubject execute request()");
    }

    public void request2() {
        System.out.println("RealSubject execute request2()");
    }

    public void request3() {
        System.out.println("RealSubject execute request3()");
    }

    public void request4() {
        System.out.println("RealSubject execute request4()");
    }
}

public class Proxy implements Subject {

    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    public void request() throws Exception{
        System.out.println("before");
        try {
            realSubject.request();
        } catch (Exception e) {
            System.out.println("ex:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("after");
        }
    }
    
    //静态代理的缺点:
    //由于Subject添加了request2()、request3()、request4()方法,代理类中也要实现相应的方法
    //而且代理对象在request2()、request3()、request4()等方法中额外需要执行的逻辑都是一样的。
    public void request2() throws Exception{
        System.out.println("before");
        try {
            realSubject.request2();
        } catch (Exception e) {
            System.out.println("ex:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("after");
        }
    }

    public void request3() throws Exception{
        System.out.println("before");
        try {
            realSubject.request3();
        } catch (Exception e) {
            System.out.println("ex:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("after");
        }
    }

    public void request4() throws Exception{
        System.out.println("before");
        try {
            realSubject.request4();
        } catch (Exception e) {
            System.out.println("ex:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("after");
        }
    }   
}

由于静态代理有上面的缺点,就产生了动态代理技术。

动态代理有两种实现方式
1)基于接口实现:JDK动态代理
2)基于继承实现:cglib动态代理

3.1.2 JDK动态代理

将上面的静态代理实现,改为使用JDK动态代理后,代码如下。

public class JdkProxySubject implements InvocationHandler {

    private RealSubject realSubject;

    public JdkProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before");
        Object result = null;
        try {
           result = method.invoke(realSubject, args);
        } catch (Exception e) {
            throw e;
        } finally {
            System.out.println("after");
        }
        return result;
    }
}

public class Client {

    public static void main(String[] args) throws Exception {
        //通过调用Proxy.newProxyInstance生成代理对象
        //方法参数为:1)classLoader  2)要代理的接口 3)代理对象的InvocationHandler
        //(通过方法参数也可以看出来,JDK代理只能通过代理接口来来实现动态代理)
        Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(),
                new Class[]{Subject.class}, new JdkProxySubject(new RealSubject()));
        //调用代理对象的request方法。
        //(根据InvocationHandler接口的定义,可以知道实际调用的是JdkProxySubject里的invoke方法)
        subject.request();
    }
}

JDK动态代理实现原理 //

3.1.3 cglib动态代理

将上面的静态代理实现,改为使用cglib动态代理后,代码如下。

public class DemoMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before in cglib");
        Object result = null;
        try {
            //注意:调用的是proxy.invokeSuper来调用目标类的方法
            result = methodProxy.invokeSuper(o, args);
        } catch (Exception e) {
            System.out.println("ex:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("after in cglib");
        }
        return null;
    }
}

public class Client {

    public static void main(String[] args) throws Exception {
        //创建一个增强器
        Enhancer enhancer = new Enhancer();
        //设置目标类
        enhancer.setSuperclass(RealSubject.class);
        //设置拦截对象
        enhancer.setCallback(new DemoMethodInterceptor());
        //生成代理对象
        Subject subject = (Subject) enhancer.create();
        //调用代理对象的request方法
        subject.request();
    }
}

3.1.4 Spring如何创建代理bean

动态代理有两种实现方式,JDK动态代理和cglib动态代理,那Spring是如何选择的?

ProxyFactoryBean的getObject()方法

@Override
public Object getObject() throws BeansException {
		initializeAdvisorChain();
    //对singleton和prototype的类型进行区分,生成对应的proxy
		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();
		}
	}

getSingletonInstance()方法

	/**
	 * Return the singleton instance of this class's proxy object,
	 * lazily creating it if it hasn't been created already.
	 * @return the shared singleton proxy
	 */
	private synchronized Object getSingletonInstance() {
		if (this.singletonInstance == null) {
			this.targetSource = freshTargetSource();
			if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
				// Rely on AOP infrastructure to tell us what interfaces to proxy.
				Class<?> targetClass = getTargetClass();
				if (targetClass == null) {
					throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
				}
				setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
			}
			// Initialize the shared singleton instance.
			super.setFrozen(this.freezeProxy);
			this.singletonInstance = getProxy(createAopProxy());
		}
		return this.singletonInstance;
	}

其中,生成代理实例的代码是这句。

	this.singletonInstance = getProxy(createAopProxy());

可以将这句代码拆成下面两句。
1)aopProxy = createAopProxy()。
2)this.singletonInstance = getProxy(aopProxy) 。

createAopProxy()

createAopProxy()的方法如下。

protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
		return getAopProxyFactory().createAopProxy(this);
	}

其中getAopProxyFactory()返回一个AopProxyFactory。在这里,返回的是AopProxyFactory的默认实现DefaultAopProxyFactory。

DefaultAopProxyFactory的createAopProxy()方法

/**
 * Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy
 * or a JDK dynamic proxy.
 *
 * <p>Creates a CGLIB proxy if one the following is true for a given
 * {@link AdvisedSupport} instance:
 * <ul>
 * <li>the {@code optimize} flag is set
 * <li>the {@code proxyTargetClass} flag is set
 * <li>no proxy interfaces have been specified
 * </ul>
 *
 * <p>In general, specify {@code proxyTargetClass} to enforce a CGLIB proxy,
 * or specify one or more interfaces to use a JDK dynamic proxy.
 */
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    //如果满足下面3个条件,将使用cglib动态代理
		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.");
			}
      //以下两种情况除外
      //targetClass是接口   或者  targetClass本身就是使用jdk动态代理的proxy
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
    //否则,使用jdk动态代理
		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])));
	}
}

在这里插入图片描述

1)如果目标对象实现了接口,则默认采用JDK动态代理
2)如果目标对象没有实现接口,则采用cglib进行动态代理
3)如果目标对象实现了接口,且强制cglib代理,则使用cglib动态代理

getProxy(aopProxy)

protected Object getProxy(AopProxy aopProxy) {
    return aopProxy.getProxy(this.proxyClassLoader);
}

如果入参中的aopProxy是JdkDynamicAopProxy的实例的话,会调用JdkDynamicAopProxy类中的方法。

如果入参中的aopProxy是ObjenesisCglibAopProxy的实例的话,会调用ObjenesisCglibAopProxy类中的方法。

JdkDynamicAopProxy.getProxy()方法

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
 
  @Override
	public Object getProxy(ClassLoader classLoader) {
		if (logger.isDebugEnabled()) {
			logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
		}
		Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
		findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
		return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
	}
}

可以看到JdkDynamicAopProxy实现了InvocationHandler接口,并通过Proxy.newProxyInstance(classLoader, proxiedInterfaces, this)生成代理对象。

ObjenesisCglibAopProxy.getProxy()方法

与下面代码逻辑类似。

 Enhancer enhancer = new Enhancer();
        //设置目标类
        enhancer.setSuperclass(RealSubject.class);
        //设置拦截对象
        enhancer.setCallback(new DemoMethodInterceptor());
        //生成代理对象
        Subject subject = (Subject) enhancer.create();
        //调用代理对象的request方法
        subject.request();

参考:
https://chuansongme.com/n/399865051525 (Spring本质系列(2)-AOP)

猜你喜欢

转载自blog.csdn.net/mccand1234/article/details/93379420