本文内容基于《Spring 5企业级开发实战》,周冠亚、黄文毅著。
1. 动态代理
动态代理是相对于静态代理而提出的设计模式。对于静态代理,一个代理类只能代理一个对象,如果有多个对象需要被代理,就需要很多代理类,造成代码冗余。动态代理的对象是动态生成的。
1.1 JDK动态代理
JDK动态代理的条件是被代理对象必须实现接口。
JDK动态代理的实现方式:
public interface Animal {
String eat();
}
public class Dog implements Animal {
@Override
public String eat() {
System.out.println("dog eat start");
return "dog eat end";
}
}
public class AnimalInvocationHandler implements InvocationHandler {
/**
* 被代理的对象
*/
private Object target;
/**
* 绑定被代理对象并返回一个代理对象
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
//被代理对象的类加载器
System.out.println(target.getClass().getClassLoader());
//被代理对象实现的接口
System.out.println(Arrays.toString(target.getClass().getInterfaces()));
//动态代理类
System.out.println(this);
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这个proxy就是动态代理类
//接收method()方法的返回值
Object result = null;
System.out.println("方法调用之前doSomething");
//相当于调用target的method()方法
result = method.invoke(target, args);
System.out.println("方法调用之后doSomething");
return result;
}
public static void main(String[] args) {
//被代理的对象
Dog dog = new Dog();
//动态代理类
AnimalInvocationHandler handler = new AnimalInvocationHandler();
//代理对象
Animal proxy = (Animal) handler.bind(dog);
System.out.println(proxy.eat());
}
}
作用就是:想要在dog对象的eat()方法前后加上额外的逻辑,可以不直接修改eat()方法。
Spring AOP的基本原理也是如此,只是Spring不需要开发人员自己维护代理类,其已帮开发人员生成了代理类。Spring AOP的实现是通过在程序运行时,根据具体的类对象和方法等信息动态地生成一个代理类的class文件的字节码,在通过ClassLoader将代理类加载到内存中,最后通过生成的代理对象进行程序的方法调用。
1.2 CGLIB动态代理
1.1中也说了,JDK动态代理的被代理对象必须要实现接口,这显然不满足开发过程中的需要。
CGLIB动态代理的实现方式:
public class Cat {
public String eat() {
System.out.println("cat eat start");
return "cat eat end";
}
}
public class CatMethodInterceptor implements MethodInterceptor {
/**
* 被代理的对象
*/
private Object target;
/**
* 绑定被代理对象并返回一个代理对象
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//接收method()方法的返回值
Object result = null;
System.out.println("方法调用之前doSomething");
//相当于调用o的method()方法
result = methodProxy.invokeSuper(o, objects);
System.out.println("方法调用之后doSomething");
return result;
}
public static void main(String[] args) {
//被代理的对象
Cat cat = new Cat();
//动态代理类
CatMethodInterceptor interceptor = new CatMethodInterceptor();
//代理对象
Cat proxy = (Cat) interceptor.bind(cat);
System.out.println(proxy.eat());
}
}
2. AOP概述
2.1 AOP基本概念
AOP是OOP的一种补充和完善:
虽然面向对象编程语言实现了纵向的对每个对象的行为进行归类和划分,实现了高度的抽象。但是,不同对象间的共性却不适合用面向对象编程的方式实现。如学生和汽车,都需要实现与其自身业务逻辑无关的监控。使用面向对象的方式就是让学生和汽车都实现监控接口,然后分别实现监控方法。这种方式的缺点就是监控并不是学生或者汽车的核心功能,并且不论是学生或者汽车或者其他什么,监控的实现基本都是一样的,这会导致大量的代码重复,并且不利于模块的复用。AOP就是用来解决这个问题的,其提供横向的切面逻辑,将与多个对象有关的公共模块封装成一个可重用模块,即切面。
2.2 Spring AOP的相关概念
- 横切关注点
一些具有横切多个不同软件模块的行为。
- 切面(Aspect)
横切关注点的抽象。
- 连接点(JoinPoint)
程序执行过程中某个特定的点。
- 切入点(Pointcut)
匹配连接点的规则,在满足这个切入点的连接点上才运行通知。
- 通知(Advice)
一些增强操作。
- 目标对象(Target Object)
需要进行增强的对象,即切面切的目标。
- 织入(Weaving)
把切面作用到目标对象,然后产生一个代理对象的过程。
- 引入(Introduction)
在运行时给一个类声明额外的方法或属性,即不需要为类实现一个接口,就能使用其中的方法。
3. Spring AOP实现
依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
3.1 基于JDK动态代理实现
- XML方式
public interface Animal {
String eat();
}
public class Dog implements Animal {
@Override
public String eat() {
System.out.println("dog eat start");
return "dog eat end";
}
}
<!--?xml version="1.0" encoding="UTF-8"?-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dog" class="com.yeta.review.c.Dog" />
<bean id="animalHandler" class="com.yeta.review.c.AnimalHandler" />
<aop:config>
<aop:aspect id="eatAspect" ref="animalHandler">
<aop:pointcut id="eatPointcut" expression="execution(* com.yeta.review.c.Animal.*(..))" />
<aop:before method="beforeHandler" pointcut-ref="eatPointcut" />
<aop:after method="afterHandler" pointcut-ref="eatPointcut" />
</aop:aspect>
</aop:config>
</beans>
public class AnimalHandler {
public void eatPointcut() {}
public void beforeHandler() {
System.out.println("before");
}
public void afterHandler() {
System.out.println("after");
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-aop.xml");
Animal dog = (Animal) context.getBean("dog");
System.out.println(dog.eat());
}
}
- 注解方式
public interface Animal {
String eat();
}
@Component
public class Dog implements Animal {
@Override
public String eat() {
System.out.println("dog eat start");
return "dog eat end";
}
}
<!--?xml version="1.0" encoding="UTF-8"?-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描注解-->
<context:component-scan base-package="com.yeta.review.c" />
<!--开启AOP注解方式-->
<aop:aspectj-autoproxy />
</beans>
@Aspect
@Component
public class AnimalHandler {
@Pointcut("execution(* com.yeta.review.c.Animal.*(..))")
public void eatPointcut() {}
@Before("eatPointcut()")
public void beforeHandler() {
System.out.println("before");
}
@After("eatPointcut()")
public void afterHandler() {
System.out.println("after");
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-aop.xml");
Animal dog = (Animal) context.getBean("dog");
System.out.println(dog.eat());
}
}
3.2 基于CGLIB动态代理实现
- XML方式
public class Cat {
public String eat() {
System.out.println("cat eat start");
return "cat eat end";
}
}
<!--?xml version="1.0" encoding="UTF-8"?-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="cat" class="com.yeta.review.c.Cat" />
<bean id="animalHandler" class="com.yeta.review.c.AnimalHandler" />
<aop:config>
<aop:aspect id="eatAspect" ref="animalHandler">
<aop:pointcut id="eatPointcut" expression="execution(* com.yeta.review.c.Cat.*(..))" />
<aop:before method="beforeHandler" pointcut-ref="eatPointcut" />
<aop:after method="afterHandler" pointcut-ref="eatPointcut" />
</aop:aspect>
</aop:config>
<!--强制使用CGLIB-->
<aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
public class AnimalHandler {
public void eatPointcut() {}
public void beforeHandler() {
System.out.println("before");
}
public void afterHandler() {
System.out.println("after");
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-aop.xml");
Cat cat = (Cat) context.getBean("cat");
System.out.println(cat.eat());
}
}
- 注解方式
@Component
public class Cat {
public String eat() {
System.out.println("cat eat start");
return "cat eat end";
}
}
<!--?xml version="1.0" encoding="UTF-8"?-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描注解-->
<context:component-scan base-package="com.yeta.review.c" />
<!--强制使用CGLIB-->
<aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
@Aspect
@Component
public class AnimalHandler {
@Pointcut("execution(* com.yeta.review.c.Cat.*(..))")
public void eatPointcut() {}
@Before("eatPointcut()")
public void beforeHandler() {
System.out.println("before");
}
@After("eatPointcut()")
public void afterHandler() {
System.out.println("after");
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-aop.xml");
Cat cat = (Cat) context.getBean("cat");
System.out.println(cat.eat());
}
}
4. 基于Spring AOP的实战
4.1 增强类型
前面提到的@Before、@After是基于AspectJ实现的增强类型,Spring也支持很多增强类型:
- 前置增强:表示在目标方法执行前实施增强;
- 后置增强:表示在目标方法执行后实施增强;
- 环绕增强:表示在目标方法执行前后实施增强;
- 异常抛出增强:表示在目标方法抛出异常后实施增强;
/**
* Spring增强测试
* 分别是MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor, ThrowsAdvice
* 注意这里的MethodInterceptor不是CGLIB动态代理的那个MethodInterceptor
* @Author YETA
* @Date 2019-07-17 17:25
*/
public class SpringBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("before");
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-aop.xml");
//被代理对象
Cat cat = (Cat) context.getBean("cat");
//前置增强
SpringBeforeAdvice springBeforeAdvice = (SpringBeforeAdvice) context.getBean("springBeforeAdvice");
//代理工厂
ProxyFactory pf = new ProxyFactory();
pf.setTarget(cat);
pf.addAdvice(springBeforeAdvice);
//代理对象
Cat proxy = (Cat) pf.getProxy();
System.out.println(proxy.eat());
}
}
- 引介增强:表示在目标类中添加一些新的方法和属性。
public class Cat {
public String run() {
System.out.println("cat run start");
return "cat run end";
}
}
<!--?xml version="1.0" encoding="UTF-8"?-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="cat" class="com.yeta.review.c.Cat" />
<bean id="someAnimal" class="com.yeta.review.c.SomeAnimal" />
<bean id="catProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interfaces="com.yeta.review.c.Animal"
p:interceptorNames="someAnimal"
p:target-ref="cat"
p:proxyTargetClass="true"/>
</beans>
public class SomeAnimal extends DelegatingIntroductionInterceptor implements Animal {
@Override
public String eat() {
System.out.println("some animal eat start");
return "some animal eat end";
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-aop.xml");
Cat catProxy = (Cat) context.getBean("catProxy");
Animal someAnimal = (Animal) catProxy;
System.out.println(someAnimal.eat());
}
}
Cat本身只有一个run()方法,通过引介增强之后,Cat的代理类多了一个eat()方法,它本身并没有实现Animal接口。
4.2 切入点类型
前面提到的@Pointcut是AspectJ的,Spring也支持很多切入点类型:
- 静态方法切入点;
- 动态方法切入点;
- 注解切入点;
- 表达式切入点;
- 流程切入点;
- 复合切入点;
- 标准切入点。
5. Spring继承AspectJ实战
AspectJ是一个面向切面的框架,其可以生成遵循Java字节码的Class文件。
Spring AOP和AspectJ之间的关系是:Spring使用了和AspectJ一样的注解,并使用AspectJ来做切入点解析和匹配。但是Spring AOP运行时并不依赖于AspectJ的编译器或者织入器等特性。
5.1 AspectJ的各种切入点指示器
自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface MyLog {
boolean value() default true;
}
用代码和注释来说明:
@Aspect
@Component
public class MyAspect {
/**
* 如果入参类型是java.lang.String则匹配
*/
@Before("args(java.lang.String)")
public void before1() {}
/**
* 如果入参类型被@MyLog注解则匹配
*/
@Before("@args(com.yeta.review.c.MyLog)")
public void before2() {}
/**
* 匹配所有com.yeta.review.c.Animal类的所有方法,包括所有参数和所有返回类型
* 第一个*是返回类型
* 第二个*是Animal类的所有方法
* (..)表示所有参数
*/
@Before("execution(* com.yeta.review.c.Animal.*(..))")
public void before3() {}
/**
* 如果目标类型是Animal,则匹配其中的所有方法
*/
@Before("target(com.yeta.review.c.Animal)")
public void before4() {}
/**
* 匹配注解了@MyLog的类
*/
@Before("@target(com.yeta.review.c.MyLog)")
public void before5() {}
/**
* 与target几乎等效,只是引介切面有差别
*/
@Before("this(com.yeta.review.c.Animal)")
public void before6() {}
/**
* 与execution类似,只是它的最小级别是类,而execution可以精确到方法的入参
*/
@Before("within(com.yeta.review.c.Animal)")
public void before7() {}
/**
* 匹配注解了@MyLog的类及其子孙类
*/
@Before("@within(com.yeta.review.c.MyLog)")
public void before8() {}
/**
* 匹配所有注解了@MyLog的类或方法
*/
@Before("@annotation(com.yeta.review.c.MyLog)")
public void before9() {}
}
6. Spring AOP的实现原理
Spring AOP的实现是通过创建目标对象的代理类,并对目标对象进行拦截来实现的。
具体源码分析参考原文。