zoukankan      html  css  js  c++  java
  • 【Spring学习笔记】AOP开发及Spring配置详解

    【Spring学习笔记】AOP及Spring配置详解

    什么是AOP,如何开发AOP开发

    AOP就是Aspect Oriented Programming,面向切面编程,是通过预编译方式和运行期间动态代理(不修改源码情况下对目标代码进行加强,实现解耦合)实现程序功能的统一维护的一种技术。

    AOP的作用及其优势

    AOP是OOP(面向对象编程)的延续,是Spring框架中的重要内容,是函数式编程的一种衍生范型,可以利用配置的方式让多个类同时具有某个功能。使用AOP可以降低业务逻辑各部分的耦合度,提高代码可重用性,同时提高开发效率。

    • 总结:
      • 作用:在程序运行期间,在不修改源码的情况下对方法进行加强
      • 优势:减少重复代码,提高开发效率,降低维护成本

    AOP的底层实现

    AOP底层是通过Spring提供的动态代理技术去实现的。在运行期间,Spring通过动态代理技术动态地生成代理对象,代理对象方法执行时进行增强功能的接入,再调用目标对象的方法,从而实现功能的增强。

    以下是其底层实现的代码,如果不是进阶深入的研究,以下代码了解即可。

    AOP的动态代理技术

    在Spring中,如果目标类实现了接口,那么使用JDK动态代理,否则使用cglib动态代理,其底层的运行机制如下图。

    JDK代理:基于接口的动态代理

    public class ProxyTest {
        public static void main(String[] args) {
            //目标对象
            final Target target = new Target();
            //增强对象
            Advice advice = new Advice();
    
            //返回值就是动态代理生成的代理对象
            TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(), //目标对象类加载器
                    target.getClass().getInterfaces(),//目标对象相同的字节码对象数组
                    new InvocationHandler() {
                        //调用代理对象的任何方法,实质上都是执行invoke方法
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            advice.before();//前置增强
                            Object invoke = method.invoke(target, args);//执行目标方法
                            advice.after();//后置增强
                            return invoke;
                        }
                    }
            );
            //调用代理对象的方法
            proxy.save();
        }
    }
    

    cglib代理:基于父类的动态代理技术

    public class ProxyTest {
        public static void main(String[] args) {
            //目标对象
            final Target target = new Target();
            //增强对象
            Advice advice = new Advice();
    
            //返回值就是动态代理生成的代理对象,基于cglib
            //1. 创建增强器
            Enhancer enhancer = new Enhancer();
            //2. 设置父类(目标)
            enhancer.setSuperclass(Target.class);
            //3. 设置回调
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    advice.before();//执行前置
                    Object invoke = method.invoke(target, args);//执行目标
                    advice.after();//执行后置
                    return invoke;
                }
            });
            //4. 创建代理对象
            Target proxy = (Target) enhancer.create();
            proxy.save();
        }
    }
    

    AOP相关概念

    以上的代码可能看不太懂,但没关系。我们只需要知道,Spring的AOP实现底层就是对以上动态代理的代码进行了封装,封装后我们只需要对关注的部分进行编写,并通过配置的方式完成指定目标的方法增强即可,并不需要我们自己编写以上的代码。

    但AOP还有相关的一些术语需要我们去掌握理解,这里将其列出来,我们先大致认识一下,在以后的学习过程中我们会越来越深入地理解这些概念:

    • Target(目标对象):代理的目标对象
    • Proxy(代理):一个类被AOP织入增强之后产生的结果代理类
    • Joinpoint(连接点):指那些被拦截到的点(可以进行增强的方法),Spring中只支持方法类型的连接点。
    • Pointcut(切入点 / 切点):指我们要对哪些Joinpoint进行拦截的定义(真正被增强的方法)。
    • Advice(通知 / 增强):对拦截到的方法额外做的事(也是一个方法)。
    • Aspect(切面):切入点和增强的结合,即被增强后的方法。
    • Weaving(织入):将切入点和增强结合的过程。指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

    AOP开发明确的事项

    需要编写的内容

    • 编写核心业务代码(即编写切点,目标类的目标方法,要被增强的方法)
    • 编写切面类,切面类中包括增强方法(即编写增强)
    • 在配置文件中配置织入关系,即将那些增强和那些连接点进行结合

    AOP技术实现的内容

    Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,就使用代理机制,动态创建目标对象的代理对象,根据增强类型,在代理对象的对应位置,将增强对应的功能织入,完成完整的代码逻辑运行。


    基于xml 的AOP开发

    开发步骤

    1. 导入AOP相关坐标
    2. 创建目标接口和目标类(内部有切点)
    3. 创建切面类(内部有增强方法)
    4. 将目标类和切面类的对象创建权交给Spring
    5. 在applicationContext.xml中配置织入关系
    6. 测试代码

    导入AOP相关坐标

    Spring本身虽然有其对AOP的实现,但相对而言,第三方的AOP配置AspectJ更为优秀,Spring官方也更推荐开发者使用AspectJ进行开发,因此此处在pom.xml中导入的是AspectJ。

    <dependency>
    	<groupId>org.aspectj</groupId>
    	<artifactId>aspectjweaver</artifactId>
    	<version>1.8.4</version>
    </dependency>
    

    创建目标接口和目标类

    目标接口TargetInterface.java

    public interface TargetInterface {
        public void save();
    }
    

    目标类Target.java

    public class Target implements TargetInterface {
        @Override
        public void save() {
            System.out.println("save...");
        }
    }
    

    创建切面类

    • 增强语法及类型

      <!-- 语法,切点表达式在文章后面有说明到 -->
      <aop:增强类型 method="切面类中的方法名" pointcut="切点表达式" />
      
      名称 标签 说明
      前置增强 <aop:before> 指定的方法在切入点方法之前执行
      后置增强 <aop:after-returning> 指定的方法在切入点方法之后执行
      环绕增强 <aop:around> 指定的方法在切入点方法之前和之后都执行
      异常抛出增强 <aop:throwing> 指定的方法在切入点方法出现异常时执行
      最终增强 <aop:after> 指定的方法无论切入点方法是否出现异常都执行

      MyAspect.java

    public class MyAspect {
        public void before() {
            System.out.println("前置增强...");
        }
    
        public void afterReturning() {
            System.out.println("后置增强...");
        }
    
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("环绕前增强...");
            Object proceed = pjp.proceed();
            System.out.println("环绕后增强...");
            return proceed;
        }
        
        public void afterThrowing() {
            System.out.println("异常抛出增强...");
        }
        
        public void after() {
            System.out.println("最终增强...");
        }
    }
    

    将目标类和切面类的对象创建权交给Spring

    将目标类和切面类的Bean对象放入Spring容器中,此时Spring将它们视为同等的Bean对象,而并不知道它们哪一个是目标对象,哪一个是切面对象,需要等后面织入关系配置完成之后Spring才能将它们区分。

    applicationContext.xml

    <!--  目标对象  -->
    <bean id="target" class="com.water.aop.Target"></bean>
    <!--  切面对象  -->
    <bean id="myAspect" class="com.water.aop.MyAspect"></bean>
    <!--  配置织入:哪些方法需要进行哪些增强(前置、后置...)  -->
    

    配置织入关系

    配置织入,就是告诉Spring哪些方法(切点)有哪些增强(前置、后置...)。
    在配置织入关系之前,我们需要导入aop的命名空间,这在我之前一篇IOC学习笔记中有相关介绍,这里不展开细讲,下面来看一下怎么配置织入关系。
    applicationContext.xml

    <aop:config>
        <!-- 声明切面 -->
        <aop:aspect ref="myAspect">
            <!-- 切面:切点 + 增强 -->
            <!-- 这里使用around增强代替before和after-returning增强
                <aop:before method="before" pointcut="execution(public void com.water.aop.*.*(..))" />
                <aop:after-returning method="afterReturning" pointcut="execution(public void com.water.aop.*.*(..))" />
                -->
            <aop:around method="around" pointcut="execution(public void com.water.aop.*.*(..))"/>
            <aop:after-throwing method="afterThrowing" pointcut="execution(public void com.water.aop.*.*(..))"/>
            <aop:after method="after" pointcut="execution(public void com.water.aop.*.*(..))"/>
        </aop:aspect>
    </aop:config>
    

    可以看到,其中的切点表达式重复出现了很多次,于是就有了相当于的解决方案:将切点表达式抽取出来,下一次要用的时候直接进行引用。这样做的好处是方便维护和简化书写。所以,以上的xml配置还可以写成下面这样。

    <aop:config>
        <!-- 声明切面 -->
        <aop:aspect ref="myAspect">
            <!-- 抽取切点表达式 -->
            <aop:pointcut id="myPointcut" expression="execution(public void com.water.aop.*.*(..))"/>
            <aop:around method="around" pointcut-ref="myPointcut"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
            <aop:after method="after" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
    

    pointcut中的特殊写法被称为切点表达式,可以指定多个切点,其写法如下:

    execution([修饰符] 返回值类型 包名.类名.方法名(参数类型) [异常])
    

    在编写切点表达式的过程有几个点需要注意:

    • 访问修饰符和异常可省略

    • 返回值类型、包名、类名、方法名可以使用星号 * 来代表任意

    • 包名与类名之间一个点,代表当前包下的类,两个点..表示当前包及其子包下的类

    • 参数列表可以使用两个点..代表任意个数,任意类型的参数列表

    下面是切点表达式的几个例子:

    //指定方法
    execution(public void com.water.aop.Target.method())
    //任意方法
    execution(void com.water.aop.Target.*)
    //指定类下的任意方法(任意返回类型、任意参数)
    execution(* com.water.aop.Target.*(..))
    //指定包下的任意类的任意方法(任意返回类型、任意参数),最常用
    execution(* void com.water.aop.*.*(..))
    //指定包及其子包下的任意类的任意方法(任意返回类型、任意参数)
    execution(* void com.water.aop..*.*(..))
    //博君一笑
    execution(* void com.water.aop..*.*(..))
    

    测试

    @RunWith(SpringJUnit4ClassRunner.class)			//让测试在Spring容器环境下执行
    @ContextConfiguration("classpath:applicationContext.xml")	//加载配置文件
    public class AppTest {
        @Autowired		//自动注入
        private TargetInterface target;
    
        @Test
        public void test1() {
            target.save();
        }
    }
    

    基于注解的AOP开发

    知道了如何通过XML进行AOP开发,我们过渡到注解开发就会变得相当容易了,不再需要各种复杂的配置,只需要简单记住几个注解的用法即可。

    AOP的注解开发步骤

    1. 使用@Aspect标注切面类
    2. 使用@通知(增强)注解标注通知方法
    3. 在配置文件中配置组件扫描和AOP自动代理<aop:aspectj-autoproxy/>
    4. 测试

    标注切面类和通知方法

    为了让Spring知道哪些类是切面,我们会使用注解标明切面类:

    @Component("myAspect")	//使用注解配置Bean对象,切面类一般也配置为@Controller
    @Aspect
    
    • 注解通知(增强)的语法及类型:
    @注解通知("切点表达式")
    
    名称 注解 说明
    前置通知 @Before 指定的方法在切入点方法之前执行
    后置通知 @AfterReturning 指定的方法在切入点方法之后执行
    环绕通知 @Around 指定的方法在切入点方法之前和之后都执行
    异常抛出通知 @AfterThrowing 指定的方法在切入点方法出现异常时执行
    最终通知 @After 指定的方法无论切入点方法是否出现异常都执行
    • 对切点表达式进行抽取

    同XML配置AOP一样,我们也可以对切点表达式进行抽取。抽取方法是在切面内定义方法(空方法即可),在该方法上使用@Pointcut注解定义切点表达式,然后在增强注解中进行引用。

    @Component("myAspect")
    @Aspect
    public class MyAspect {
    	@Before("MyAspect.myPoint()")
        public void before() {
            System.out.println("前置增强...");
        }
        @Pointcut("execution(* com.water.anno.*.*(..))")
        public void myPoint() {}
    }
    

    配置组件扫描和AOP自动代理

    <!--  组件扫描  -->
    <context:component-scan base-package="com.water.anno"/>
    <!--  AOP自动代理  -->
    <aop:aspectj-autoproxy/>
    
  • 相关阅读:
    (8)Normalization
    (7)Drop out/Drop block
    (6)data augmentation——遮挡
    (5)label smooth
    (4)Focal loss
    (3)data augmentation——pixel-wise
    ostringstream 性能测试
    CPU & 多线程
    PC 常备软件(windows)
    编译器前端简介
  • 原文地址:https://www.cnblogs.com/tuzkizki/p/14845967.html
Copyright © 2011-2022 走看看