Spring Aop学习笔记

AOP 简介

AOP的概念

AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程

AOP思想的实现方案

模拟AOP的基础代码

其实在之前学习BeanPostProcessor时,在BeanPostProcessor的after方法中使用动态代理对Bean进行了增强,实际存储到单例池singleObjects中的不是当前目标对象本身,而是当前目标对象的代理对象Proxy,这样在调用目标对象方法时,实际调用的是代理对象Proxy的同名方法,起到了目标方法前后都进行增强的功能,对该方式进行一下优化,将增强的方法提取出去到一个增强类中,且只对com.itheima.service.impl包下的任何类的任何方法进行增强
// 自定义增强类
public class MyAdvice {
        public void beforeAdvice(){
                System. out .println( "beforeAdvice ..." );
        }
        public void afterAdvice(){
                System. out .println( "afterAdvice ..." );
        }
}
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
    private ApplicationContext applicationContext;//注入Spring容器对象
    public Object postProcessAfterInitialization(Object bean, String beanName) throws         BeansException {
        MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class);//获得Advice对象
        String packageName = bean.getClass().getPackage().getName();
        if("com.itheima.service.impl".equals(packageName)){
            //对Bean进行动态代理,返回的是Proxy代理对象
            Object proxyBean = Proxy.newProxyInstance(
            bean.getClass().getClassLoader(),
            bean.getClass().getInterfaces(),
            (Object proxy, Method method, Object[] args) -> {
                myAdvice.beforeAdvice();//执行Advice的before方法
                Object result = method.invoke(bean, args);//执行目标
                myAdvice.afterAdvice();//执行Advice的after方法
                return result; });
            //返回代理对象
            return proxyBean; }
            return bean; }
    public void setApplicationContext(ApplicationContext applicationContext) throws     BeansException {
        this.applicationContext = applicationContext; 
}}

AOP相关概念

基于xml配置的AOP

xml方式AOP快速入门

前面我们自己编写的AOP基础代码还是存在一些问题的,主要如下:
        被增强的包名在代码写死了
        通知对象的方法在代码中写死了
通过配置文件的方式去解决上述问题
        配置哪些包、哪些类、哪些方法需要被增强
        配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强
配置方式的设计、配置文件(注解)的解析工作,Spring已经帮我们封装好了
xml方式配置AOP的步骤:
1、导入AOP相关坐标;
2、准备目标类、准备增强类,并配置给Spring管理;
3、配置切点表达式(哪些方法被增强);
4、配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。
1、导入AOP相关坐标
< dependency >
        < groupId >org.aspectj</ groupId >
        < artifactId >aspectjweaver</ artifactId >
        < version >1.9.6</ version >
</ dependency >
Spring-context坐标下已经包含spring-aop的包了,所以就不用额外导入了
2、准备目标类、准备增强类,并配置给Spring管理
public interface UserService {
        void show1();
        void show2();
}
public class UserServiceImpl implements UserService {
        public void show1() {
                System. out .println( "show1..." );
        }
        public void show2() {
                System. out .println( "show2..." );
        }
}
 
public class MyAdvice {
        public void beforeAdvice(){
                System. out .println( "beforeAdvice" );
        }
        public void afterAdvice(){
                System. out .println( "afterAdvice" );
        }
}
<!-- 配置目标类 , 内部的方法是连接点 -->
< bean id= "userService" class= "com.itheima.service.impl.UserServiceImpl" />
<!-- 配置通知类 , 内部的方法是增强方法 -->
< bean id= myAdvice" class= "com.itheima.advice.MyAdvice" />
3、配置切点表达式(哪些方法被增强)
4、配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)
< aop :config >
        <!--配置切点表达式 , 对哪些方法进行增强 -->
        < aop :pointcut id= "myPointcut" expression= "execution(void com.itheima.service.impl.UserServiceImpl.show1())" />
        <!--切面 = 切点 + 通知 -->
        < aop :aspect ref= "myAdvice" >
                <!--指定前置通知方法是beforeAdvice-->
                < aop :before method= "beforeAdvice" pointcut-ref= "myPointcut" />
                <!--指定后置通知方法是afterAdvice-->
                < aop :after-returning method= "afterAdvice" pointcut-ref= "myPointcut" />
        </ aop :aspect >
</ aop :config >

xml方式AOP配置详解

xml配置AOP的方式还是比较简单的,下面看一下AOP详细配置的细节:
        1.切点表达式的配置方式
        2.切点表达式的配置语法
        3.通知的类型
        4.AOP的配置的两种方式
切点表达式的配置方式有两种,直接将切点表达式配置在通知上,也可以将切点表达式抽取到外面,在通知上进行引用
< aop :config >
        <!--配置切点表达式 , 对哪些方法进行增强 -->
        < aop :pointcut id= "myPointcut" expression= "execution(void com.itheima.service.impl.UserServiceImpl.show1())" />
        <!--切面 = 切点 + 通知 -->
        < aop :aspect ref= "myAdvice" >
                <!--指定前置通知方法是beforeAdvice-->
                < aop :before method= "beforeAdvice" pointcut-ref= "myPointcut" />
                <!--指定后置通知方法是afterAdvice-->
                < aop :after-returning method= "afterAdvice" pointcut= "execution(void com.itheima.service.impl.UserServiceImpl.show1())" />
        </ aop :aspect >
</ aop :config >
切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:
execution([ 访问修饰符 ] 返回值类型 包名 . 类名 . 方法名 ( 参数 ))
配置规则
1.访问修饰符可以省略不写;
2.返回值类型、某一级包名、类名、方法名 可以使用 * 表示任意;
3.包名与类名之间使用单点 . 表示该包下的类,使用双点 .. 表示该包及其子包下的类;
4.参数列表可以使用两个点 .. 表示任意参数。
切点表达式举几个例子方便理解
// 表示访问修饰符为 public 、无返回值、在 com.itheima.aop 包下的 TargetImpl 类的无参方法 show
execution(public void com.itheima.aop.TargetImpl.show())
// 表述 com.itheima.aop 包下的 TargetImpl 类的任意方法
execution(* com.itheima.aop.TargetImpl.*(..))
// 表示 com.itheima.aop 包下的任意类的任意方法
execution(* com.itheima.aop.*.*(..))
// 表示 com.itheima.aop 包及其子包下的任意类的任意方法
execution(* com.itheima.aop..*.*(..))
// 表示任意包中的任意类的任意方法
execution(* *..*.*(..))
AspectJ的通知由以下五种类型
环绕通知
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        //环绕前
        System. out .println( " 环绕前通知 " );
        //目标方法
        joinPoint.proceed();
        ///环绕后
        System. out .println( " 环绕后通知 " );
}
< aop :around method= "around" pointcut-ref= "myPointcut" />
异常通知,当目标方法抛出异常时,异常通知方法执行,且后置通知和环绕后通知不在执行
public void afterThrowing(){
System. out .println( " 目标方法抛出异常了,后置通知和环绕后通知不在执行 " );
}
< aop :after-throwing method= "afterThrowing" pointcut-ref= "myPointcut" />
最终通知,类似异常捕获中的finally,不管目标方法有没有异常,最终都会执行的通知
public void after(){
        System. out .println( " 不管目标方法有无异常,我都会执行 " );
}
< aop :after method= "after" pointcut-ref= "myPointcut" />
通知方法在被调用时,Spring可以为其传递一些必要的参数
JoinPoint 对象
public void 通知方法名称 (JoinPoint joinPoint){
        //获得目标方法的参数
        System. out .println(joinPoint.getArgs());
        //获得目标对象
        System. out .println(joinPoint.getTarget());
        //获得精确的切点表达式信息
        System. out .println(joinPoint.getStaticPart());
}
ProceedingJoinPoint对象
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System. out .println(joinPoint.getArgs()); // 获得目标方法的参数
        System. out .println(joinPoint.getTarget()); // 获得目标对象
        System. out .println(joinPoint.getStaticPart()); // 获得精确的切点表达式信息
        Object result = joinPoint.proceed(); // 执行目标方法
        return result; // 返回目标方法返回值
}
Throwable对象
public void afterThrowing(JoinPoint joinPoint,Throwable th){
        //获得异常信息
        System. out .println( " 异常对象是: " +th+ " 异常信息是: " +th.getMessage());
}
< aop :after-throwing method= "afterThrowing" pointcut-ref= "myPointcut" throwing= "th" />
AOP的另一种配置方式,该方式需要通知类实现Advice的子功能接口
public interface Advice {
}

注解方式AOP配置详解

Advice的子功能接口
例如:通知类实现了前置通知和后置通知接口
public class Advices implements MethodBeforeAdvice, AfterReturningAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
        System. out .println( "This is before Advice ..." );
        }
        public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws
        Throwable {
        System. out .println( "This is afterReturn Advice ..." );
        }
}
切面使用advisor标签配置
< aop :config >
        <!-- 将通知和切点进行结合 -->
        < aop :advisor advice-ref ="advices" pointcut ="execution(void
        com.itheima.aop.TargetImpl.show())" />
</ aop :config >
又例如:通知类实现了方法拦截器接口
public class MyMethodInterceptor implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System. out .println( " 前置逻辑功能 ..." );
        //执行目标方法
        Object invoke =
        methodInvocation.getMethod().invoke(methodInvocation.getThis(),methodInvocation.getArguments());
        System. out .println( " 后置逻辑功能 ..." );
        return invoke;}
}
切面使用advisor标签配置
< aop :config >
        <!-- 将通知和切点进行结合 -->
        < aop :advisor advice-ref = myMethodInterceptor" pointcut ="execution(void
        com.itheima.aop.TargetImpl.show())" />
</ aop :config >
使用aspect和advisor配置区别如下:
<!-- 使用 advisor 配置 -->
< aop :config >
        <!-- advice-ref:通知 Bean id -->
        < aop :advisor advice-ref ="advices" pointcut ="execution(void
com.itheima.aop.TargetImpl.show())" />
</ aop :config >
<!-- 使用 aspect 配置 -->
< aop :config >
        <!-- ref:通知 Bean id -->
        < aop :aspect ref ="advices" >
< aop :before method ="before" pointcut ="execution(void
com.itheima.aop.TargetImpl.show())" />
        </ aop :aspect >
</ aop :config >
2)通知类的定义要求不同,advisor 需要的通知类需要实现Advice的子功能接口:
public class Advices implements MethodBeforeAdvice {
        public void before(Method method, Object[] objects, Object o) throws Throwable {
                System. out .println( "This is before Advice ..." );
        }
        public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws
                Throwable {
                System. out .println( "This is afterReturn Advice ..." );
        }
}
aspect 不需要通知类实现任何接口,在配置的时候指定哪些方法属于哪种通知类型即可,更加灵活方便:
public class Advices {
        public void before() {
                System. out .println( "This is before Advice ..." );
        }
        public void afterReturning() {
                System. out .println( "This is afterReturn Advice ..." );        
        }
}
3)可配置的切面数量不同:
一个advisor只能配置一个固定通知和一个切点表达式;
一个aspect可以配置多个通知和多个切点表达式任意组合,粒度更细。
4)使用场景不同:
1.如果通知类型多、允许随意搭配情况下可以使用aspect进行配置;
2.如果通知类型单一、且通知类中通知方法一次性都会使用到的情况下可以使用advisor进行配置;
3.在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如后面要学习的Spring事务控制的配置;
由于实际开发中,自定义aop功能的配置大多使用aspect的配置方式,所以我们后面主要讲解aspect的配置,advisor是为了后面Spring声明式事务控制做铺垫。

xml方式AOP原理剖析

通过xml方式配置AOP时,我们引入了AOP的命名空间,根据讲解的,要去找spring-aop包下的META-INF,在去找spring.handlers文件
http\://www.springframework.org/schema/aop = org.springframework.aop.config.AopNamespaceHandler
最终加载的是 AopNamespaceHandler,该Handler的init方法中注册了config标签对应的解析器
this .registerBeanDefinitionParser( "config" , new ConfigBeanDefinitionParser());
以ConfigBeanDefinitionParser作为入口进行源码剖析,最终会注册一个AspectJAwareAdvisorAutoProxyCreator进入到Spring容器中,那该类作用是什么呢?看一下集成体系图
AspectJAwareAdvisorAutoProxyCreator 的上上级父类AbstractAutoProxyCreator中的
postProcessAfterInitialization方法
// 参数 bean :为目标对象
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null ) {
                Object cacheKey = this .getCacheKey(bean.getClass(), beanName);
                 if ( this .earlyProxyReferences.remove(cacheKey) != bean) {
                        //如果需要被增强,则wrapIfNecessary方法最终返回的就是一个 Proxy 对象
                        return this .wrapIfNecessary(bean, beanName, cacheKey);
                }
        }
        return bean;
}
通过断点方式观察,当bean是匹配切点表达式时,this.wrapIfNecessary(bean, beanName, cacheKey)返回的是一个JDKDynamicAopProxy
高级软件人才培训专家
2211
ublic class MyAdvice {
  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值