目录
spring 是一个流行的 Java 企业应用程序开发框架,其中的 aop(面向切面编程)是 spring 框架中一个非常重要的概念。可以说在spring框架以及生态体系下,随处可见aop编程思想的运用,借助这一编程思想,在很多特殊的业务场景下,aop的使用让编码变得易扩展、更优雅、更灵活,同时也能很好的解决通用的业务问题,提升开发效率。本文将详细介绍aop的核心技术和底层实现原理。
AOP,即面向切面编程,AOP是一种编程范式,用于在不修改原始代码的情况下向现有应用程序添加新功能。这种编程方式将应用程序分成许多独立的部分,称为切面。这些切面可以在应用程序的不同位置进行编写和维护,从而提高了应用程序的可重用性和可维护性。
AOP是OOP(面向对象编程)的延续,主要用于实现横切关注点,比如:日志记录、性能统计、安全控制、事务处理等方面。简单来说,针对应用程序中那些可以预见的,以及归总为公共的处理业务的场景均可以考虑使用aop的编程实现。
为了后续更全面深入的掌握aop的使用,有必要了解下spring aop的相关概念。这些概念能够更好的指导我们在编码中对aop的原理的理解。
切面(Aspect)
AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。
连接点(Join Point)
AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。
连接点(Join Point)
哪些方法需要被AOP增强,这些方法就叫做连接点
通知(Advice)
AOP在特定的切入点上执行的增强处理
常用的通知类型有:
切入点(Pointcut)
实际真正被增强的方法,称为切入点
通知(advice)是你希望在横切关注点具体的实现方式,即方法之前触发?执行后触发...,aop中的Advice主要有以下5种类型:
前置通知(Before Advice)
在连接点之前执行的Advice,即方法执行前,使用@Before注解使用这个Advice
返回之后通知(After Retuning Advice)
在连接点正常结束之后执行的Advice,即你的方法正常执行完成之后执行,通过 @AfterReturning注解使用它,前提是你的方法没有抛出异常
抛出(异常)后执行通知(After Throwing Advice)
如果方法执行抛异常的时候,这个Advice就会被执行,通过 @AfterThrowing注解来使用
后置通知(After Advice)
无论连接点通过什么方式退出(方法正常返回或者抛出异常)都会执行在结束后执行这些Advice,通过 @After注解使用
围绕通知(Around Advice)
围绕连接点执行的Advice,方法执行前可以拦截参数,方法执行后可以拿到执行结果,属于几种通知中比较灵活的一种,通过@Around注解使用
Spring AOP 的实现原理是基于动态代理和字节码操作的。具体来说,
Spring AOP 主要使用两种代理方式:JDK动态代理和 CGLIB 代理。如果目标对象实现了至少一个接口,则框架使用JDK动态代理;否则,使用 CGLIB 代理。
基于接口情况下,使用JDK动态代理
JDK动态代理,创建一个接口实现类的代理对象,该接口实现类的代理对象会调用该接口的真实实现,并且在代理对象中调用真实的实现类的方法进行增强
没有接口情况下,使用 CGLIB 动态代理
CGLIB动态代理,创建一个类子类的代理对象,该子类的代理对象会去调用父类中的方法,并且在子类代理对象调用其父类方法后做增强
很多同学在理解aop的代理时,很容易对静态代理与动态代理这个概念搞混,顾名思义,静态代理,简单理解就是代理的类或方法定义已经提前定义好了,在需要使用的地方直接调用即可,而动态代理,关键是理解动态这个概念,从程序的角度来说,就是在程序运行过程中,动态生成了目标对象的代理对象。
定义一个接口
public interface LoginService {
void login(String userId);
}
接口实现
public class LoginServiceImpl implements LoginService {
@Override
public void login(String userId) {
System.out.println("登录成功,userId:"+userId);
}
}
实现了LoginService 接口的代理实现类
public class ProxyLoginService implements LoginService {
private LoginServiceImpl loginServiceImpl;
public ProxyLoginService(LoginServiceImpl loginServiceImpl){
this.loginServiceImpl=loginServiceImpl;
}
@Override
public void login(String userId) {
System.out.println("执行登录之前操作");
loginServiceImpl.login("user");
System.out.println("执行登录方法后操作");
}
}
测试方法
public static void main(String[] args) {
//创建电影院(静态代理)
ProxyLoginService proxyLoginService = new ProxyLoginService(new LoginServiceImpl());
proxyLoginService.login("user1");
}
静态代理优点
缺点:
与静态代理不同,动态代理是根据代理对象,动态创建代理类。这样就可以避免静态代理中代理类接口过多的问题。jdk动态代理是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。
jdk动态代理的使用步骤如下:
自定义一个类,实现InvocationHandler接口,并重写里面的invoke方法
public class ProxyInvocationHandler implements InvocationHandler {
//需要动态代理接口的真实实现类
private Object object;
//通过构造方法去给需要动态代理接口的真实实现类赋值
public ProxyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法增强
System.out.println("方法执行前操作");
//object是真实实现,args是调用方法的参数
//当代理对象调用真实实现的方法,那么这里就会将真实实现和方法参数传递过去,去调用真实实现的方法
method.invoke(object,args);
//方法增强
System.out.println("方法执行后操作");
return null;
}
}
编写测试类,使用自定义的handler生成代理对象并调用目标方法
public class DynamicProxyTest {
public static void main(String[] args) {
//需要动态代理接口的真实实现
LoginServiceImpl realMovie = new LoginServiceImpl();
//动态代理处理类
ProxyInvocationHandler handler = new ProxyInvocationHandler(realMovie);
//获取动态代理对象
//第一个参数:真实实现(被代理对象)的类加载器
//第二个参数:真实实现类(被代理对象)它所实现的所有接口的数组
//第三个参数:动态代理处理器
LoginService loginService = (LoginService) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(),
realMovie.getClass().getInterfaces(),
handler);
loginService.login("user");
}
}
执行结果
由于jdk的代理是在运行期间动态产生字节码,所以很难看到运行期间代理产生的源码,我们可以模拟其原理进行一个简单的实现,代码如下:
定义一个接口,作为被代理的对象,实现类也一并贴出
interface MinuService {
int addMinu();
void reduceMinu();
}
//目标对象实现类
static class Target implements MinuService {
@Override
public int addMinu() {
System.out.println("tager do addMinu");
return 1;
}
@Override
public void reduceMinu() {
System.out.println("tager do reduceMinu");
}
}
定义一个handler,handler的作用可理解为作为代理对象中实际执行方法调用的一个入口
interface MyInvocationHandler {
Object invoke(Object proxy,Method method, Object[] args);
}
定义代理类,代理类是核心实现,在代理类中主要做的事情如下:
import java.lang.reflect.Method;
public class MyProxy implements JdkInner.MinuService {
private JdkInner.MyInvocationHandler handler;
public MyProxy(JdkInner.MyInvocationHandler handler) {
this.handler = handler;
}
static Method addMinu;
static Method reduceMinu;
static {
try {
addMinu = JdkInner.MinuService.class.getMethod("addMinu");
reduceMinu = JdkInner.MinuService.class.getMethod("reduceMinu");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
public int addMinu() {
Object invoke = null;
invoke = handler.invoke(this, addMinu, new Object[0]);
return Integer.parseInt(invoke.toString());
}
@Override
public void reduceMinu() {
handler.invoke(this, reduceMinu, new Object[0]);
}
}
编写测试类,有没有发现这个写法和上述使用jdk的动态代理很像
public static void main(String[] args) {
MinuService minuService = new MyProxy(new MyInvocationHandler() {
@Override
public Object invoke(Object proxy,Method method, Object[] args) {
System.out.println("before invoke method");
Object result = null;
try {
result = method.invoke(new Target(), args);
System.out.println("result :" + result);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return result;
}
});
System.out.println(minuService.getClass());
minuService.addMinu();
}
如果你想查看jdk动态代理时产生的源码,可以考虑使用arthars进行反编译查看
CGLIB 代理是一个基于字节码操作的代理方式,它是一个强大的、高性能、高质量的 Code 生成类库,可以在运行期扩展 Java 类与实现 Java 接口,可以为没有实现接口的类创建代理对象。CGLIB 代理会在运行时生成一个目标对象的子类,并覆盖其中的方法,以实现AOP的功能。下面是 CGLIB 代理的实现代码:
public class CglibAopProxy implements AopProxy {
private final AdvisedSupport advised;
public CglibAopProxy(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(advised.getTargetSource().getTargetClass());
enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
return enhancer.create();
}
private static class DynamicAdvisedInterceptor implements MethodInterceptor {
private final AdvisedSupport advised;
public DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
MethodInvocation methodInvocation = new CglibMethodInvocation(
advised.getTargetSource().getTarget(),
method,
args,
proxy,
advised.getMethodInterceptor(),
advised.getTargetSource().getTargetClass()
);
return methodInvocation.proceed();
}
}
}
下面是一段使用cglib进行代理的代码
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibTest {
static class Target {
public void get() {
System.out.println("target get");
}
}
public static void main(String[] args) {
Target target = new Target();
//拿到代理的类
Target proxyTarget = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
@Override
public Object intercept(Object proxyClass, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before....");
Object result = method.invoke(target, args);
System.out.println("after...");
return result;
}
});
//执行代理类的方法
proxyTarget.get();
}
}
结果看起来和使用jdk代理类似
与jdk代理不同的是,jdk的代理仅能针对接口代理,而cglib生成的代理对象是一个子类,所以需要注意,使用cglib进行代理的时候,父类不能是final的,并且目标类中的代理方法也不能是final的。
上面的案例了解了如何使用cglib进行代理以及代码的实现,下面来模拟一下其底层源码的实现
定义一个目标类
public class CglibTarget {
public void add(){
System.out.println("add()");
}
public void add(int num){
System.out.println("add() :" + num);
}
public void reduce(int count){
System.out.println("reduce() :" + count);
}
}
定义代理类
通过上面的介绍了解到,cglib的代理对象是通过生成目标对象的子类实现的,所以代理类需要继承目标类
import org.springframework.cglib.proxy.MethodInterceptor;
import java.lang.reflect.Method;
public class CglibProxy extends CglibTarget {
private MethodInterceptor methodInterceptor;
public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
static Method add_1;
static Method add_2;
static {
try {
add_1 = CglibTarget.class.getMethod("add");
add_2 = CglibTarget.class.getMethod("add",int.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
public void add() {
try {
methodInterceptor.intercept(this,add_1,new Object[0],null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
public void add(int num){
try {
methodInterceptor.intercept(this,add_2,new Object[]{num},null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
public void reduce(int count){
super.reduce(count);
}
}
测类类
import com.congge.aop.cglib.CglibProxy;
import com.congge.aop.cglib.CglibTarget;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibTest {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
CglibTarget target = new CglibTarget();
proxy.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object targetObj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before handle");
Object result = method.invoke(target, args);
System.out.println("after handle");
return result;
}
});
proxy.add(10);
}
}
运行代码,得到如下效果
在使用cglib代码编码实现中,注意到在intercept方法参数中有一个Method的参数,这个参数是做什么用的呢?
不妨将代码修改成下面这样
public static void main(String[] args) {
Target target = new Target();
//拿到代理的类
Target proxyTarget = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
@Override
public Object intercept(Object proxyClass, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before....");
//Object result = method.invoke(target, args);
Object result = methodProxy.invoke(target, args);
System.out.println("after...");
return result;
}
});
//执行代理类的方法
proxyTarget.get();
}
再次执行,发现仍然可以得到正确的结果,为什么会这样呢?
cglib代理底层通过这种方式为调用者提供了多一种选择,当选择使用methodProxy的invoke方法时,将不反射,而是退化为直接使用原始目标对象去调用方法,某些情况下,可以获得比反射更高的性能。
spring底层在aop的代理上是怎么处理的呢,接下来让我们通过源码一探究竟。
只需要引入aspectjweaver即可
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
定义一个切面的配置类,这里扫描的是某个包路径下的所有方法,并且使用了环绕通知,在通知方法里,输出了方法实际执行耗时
@Component
@Aspect
public class AspectConfig {
@Pointcut("execution(* com.congge.service.aop.*.*(..)))")
private void pointcut(){
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = point.proceed();
long end = System.currentTimeMillis();
System.out.println("消耗时间:" + (end-start));
return proceed;
}
}
编写测试方法,验证上述aop切面通知是否生效
@ComponentScan("com.congge.service.aop")
@EnableAspectJAutoProxy
public class SpringApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringApp.class);
UserService userService = context.getBean(UserService.class);
userService.getUser();
}
}
运行之后效果如下,说明aop的环绕通知配置生效了
在实际开发中,aop编程的一般步骤是,定义切面类,然后定义具体的切点表达式,然后再在具体的通知方法上面引入切点表达式即可。下面列举几种常用的几种自定义切点表达式的写法。
例1:对扫描包中的某个类的方法增强
execution(* com.xxx.xxxClass.方法名(..))
例2:对扫描包中的某个类的所有方法增强
?execution(* com.xxx.xxxClass.* (..))
例3:对扫描包中所有类,类中所有方法增强
?execution(* com.xxx.service.*.* (..))
例4:所有带有某个注解的方法或类
@annotation(com.xxx.annotation.xxxAnnotation)
上面举的例子都是去切入具体的某个方法、类,也可以切到某个包下所有的方法,也可以去切某包下带有某注解的方法等等。 ?
下面列举常用的几种切点表达式示例,便于后续的参考
?所有方法执行
execution(public * *(..))
名以"XXX"开头的所有方法执行
execution(* XXX*(..))
XXX接口中所有方法执行
?execution(* com.xxx.XXX.*(..))
service包下所有方法执行
?execution(* com.xxx.service..*.*(..))
service包下的所有连接点
?(仅在Spring AOP中执行方法)
within(com.xxx.service..*)
代理实现XXX接口的任何连接点
?(仅在Spring AOP中执行方法)
this(com.xxx.service.XXXService)
所有带有@XXX注解的方法或类
@annotation(com.xxx.annotation.XXX)
通过断点,在真正执行userService.getUser()之前,我们可以看到,userService已经是一个代理对象,而且不难看出,这是一个jdk动态代理产生的对象(userService接口存在实现类)
于是可以断定,在真正执行目标方法的时候,是代理对象在帮我们执行了,那么代理对象在哪里产生的呢?代理对象是什么时候产生的呢?这就是接下来需要搞清的重点所在。
从getBean入手,一路往下走,通过getBean方法,最终获取到了代理对象
public <T> T getBean(Class<T> requiredType) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(requiredType);
}
通过getBean方法走到下面这个doGetBean方法里,在这个方法中,注意有一个关键的位置:getSingleton,很多同学在看spring源码的时候发现调用栈非常深,经常陷入一种不知道从何看起的状态,这里说过小技巧,你只需要关注你期望得到的目标即可。比如在doGetBean这里,为了快速获取到userService,可以在此处设置条件断点,如下:
以上述getSingleton为例来说,通过这个方法,可能会得到代理对象,需不需要进去看呢,可以先走一步,如果得到的是代理对象,那么说明getSingleton这个方法中是产生代理对象的地方,按照这个思路,我们走一步看看,发现此刻得到的对象就是一个代理对象,说明确实是在getSingleton这个方法中产生了代理对象;
以getSingleton继续深入,进入该方法之后发现,从singletonObjects中拿到的就是代理对象了,这里不禁冒出一个问题来了,singletonObjects中是什么时候将代理对象放进去的呢?
这里不得不说另一个源码的调试和阅读技巧,就是直接看debug中的调用栈,从下面的调用栈可以发现,上一步singletonObjects中存储的这个代理对象就是在其中的某一步产生并放到容器中的;
?这样一来,至少可以说明,产生代理对象的时机在当前这个getSingleton之前,从spring的bean的生命周期来看,要经历解析,创建,初始化,实例化等一些步骤,所以可以将目光定位到创建的过程,即createBean阶段,于是从getBean方法入手作为突破点即可,再次断点进入,来到getBean方法中
通过getBean一路来到getSingleton方法中,该方法即创建初始的userService的核心代码,而且第一次进入的时候发现userService的bean对象还未创建出来,一直来到该方法的如下位置,利用上面的调试技巧发现就是在这个singletonFactory.getObject()方法得到了代理对象
继续进入这个singletonFactory.getObject()方法,就来到创建bean的方法中,即createBean
沿着该方法继续往下走,当走到doCreateBean这里时,发现这个方法执行结束后就成了代理对象
继续进入doCreateBean方法中,当走完initializeBean方法之后,发现就产生了代理对象
那么initializeBean里面发生了什么呢?可以断定这个方法的某个地方最终产生了代理对象,于是来到下面这个方法
进入到applyBeanPostProcessorsAfterInitialization方法中
来到postProcessAfterInitialization里面之后,沿着wrapIfNecessary继续进入
?
历经千辛万苦,最终来到这里,这个方法就是最终产生代理对象的地方,核心创建代理对象的代码就是下面这段
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
}
?我们重点关注方法中createProxy这个方法即可
跳过中间的步骤,最终来到createAopProxy方法中,在这个方法中,展示了如何创建代理对象,以及采用哪种方式创建代理对象,即使用jdk动态代理呢?还是cglib方式呢?
从这段代码不难看出,这里做了一个判断,如果目标对象是接口,将采用jdk动态代理,如果目标对象是类,则使用cglib的代理,由于在前面的代码中目标对象是普通的类,所以将会产生一个cglib的代理对象
补充说明: ?
在上面判断使用哪种代理方式时,有一个很重要的参数,即判断下面的这个参数的布尔值,通过源码点击进去发现,默认情况下该参数初始值为false
config.isProxyTargetClass()
该参数的意义在于,开发者可以通过强制指定这个参数的值,从而改变代理的方式强制使用cglib,如何修改呢,只需要将下面的注解中改为true即可
本文通过较大的篇幅全面而深度的总结了aop代理相关的技术点,并且深入到源码层面解读了spring aop的完整过程,aop不仅是spring编程中的重要内容,也是日常开发中运用比较广泛的技术点,有必要对其做深入的理解和掌握。本篇到此结束,感谢观看。