首页 > 技术文章 > 重温Spring之AOP

VVII 2020-02-19 23:08 原文

一、AOP理解

  • 面向切面编程,是OOP面向对象编程的补充。将程序中交叉的业务逻辑代码提取出来,封装成切面,由AOP容器在适当的时机将封装的切面动态的织入到具体业务逻辑中。

AOP原理-->使用代理

  • 对于实现接口的目标类,使用jdk动态代理
  • 对于没有实现任何接口的类,使用cglib代理

术语

  • 连接点 joinpoint
    程序执行中某一特定位置,eg 方法调用前或后,方法抛出异常
  • 切入点 pointcut
    定位查找到需要的连接点,一个切点可以包含多个切入点
  • 增强 Advice 也成为通知
    在切点上执行的一段代码,用来实现某些功能
  • 目标对象 target
    将执行增强处理的目标类
  • 织入 weaving
    将增强添加到目标类中具体切入点的过程
  • 代理
    一个类被织入增强后,会产生一个代理类,这个类包含原类以及增强
  • 切面
    切点和增强的组合
  • 引介/引入 introduction


二、代理模式

概念

  • 为其他对象提供一种代理,以控制对这个对象的访问,起到中介的作用
    通过代理对象访问目标对象,可以增强额外的操作,扩展目标对象的功能

静态代理

  • 手动创建
    代理对象需要与目标类实现相同接口,维护麻烦


动态代理

  • 由JVM根据反射动态生成,程序运行前不存在代理类的字节码文件

JDK

目标对象必须实现一个或多个接口

  • eg
    @Test
    public void test() {
        UserSrv userSrv = (UserSrv) Proxy.newProxyInstance(
                UserSrvImpl.class.getClassLoader(),//目标类的类加载器
                new Class[]{UserSrv.class},//目标类的接口列表
                new InvocationHandler() {//交叉业务逻辑
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 打印日志
                        System.out.println(method.getName() + " 打印日志 ---" );
                        //执行业务逻辑
                       Object o =  method.invoke(new UserSrvImpl(),args);
                        System.out.println(o);
                        System.out.println();
                        return o;
                    }
                });

        userSrv.login("XIXI","0000");
        System.out.println(" ---------- ---------- --------");
        userSrv.logout();
        System.out.println(userSrv.getClass());
    }
public class UserSrvImpl implements UserSrv{
    @Override
    public String login(String usrNm,String pwd) {
        return usrNm +"login success at "+ new Date().getTime();
    }

    @Override
    public String logout() {
        return "now 一位用户离开------ ";
    }
}

CGLIB

  • eg
 <!-- https://mvnrepository.com/artifact/cglib/cglib -->
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>${cglib.version}</version>
            </dependency>
    @Test
    public void testCglib(){
        App app = (App) Enhancer.create(
                App.class, // 目标类的类型
                new net.sf.cglib.proxy.InvocationHandler() { //交叉业务逻辑
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                        System.out.println(method.getName() + " start  ---- ");
                        return method.invoke(new App(),objects);
                    }
                }
        );

        app.HelloWord(new String[]{"XIXI"});

        //代理类的类型
        System.out.println(app.getClass());
    }
public class App {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }

    public String HelloWord(String[] args) {
        System.out.println();
        System.out.println("args: "+ Arrays.toString(args));
        System.out.println("null list: " + Arrays.asList(new ArrayList<String>()));
        System.out.println();
        return "Hello World!";
    }
}


三、AOP配置方式

Spring AOP 1.X (了解,有助于理解AOP)

  • 使用ProxyFactoryBean手动代理

五种增强(/通知)类型

通知类型 实现接口 描述 备注
前置通知 MethodBeforeAdvice 在方法执行前添加功能
后置通知 AfterReturningAdvice 在方法执行后添加功能
环绕通知 MethodInterceptor 在方法执行前后添加功能 org.aopalliance.intercept.MethodInterceptor
异常通知 ThrowsAdvice 在方法抛出异常后添加功能
引入通知 IntroductionInterceptor 在目标类中添加新方法和属性 了解

实例

  • 1.配置pom
            <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
                <version>${spring.version}</version>
            </dependency>
  • 2.配置Advice
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="logAdvice" class="org.example.LogAdvice"></bean>
</beans>
  • 3.定义增强类,实现相应接口
public class LogAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("method "+method.getName()+" args: "+ Arrays.toString(objects) + " target: " +o );
    }
}
  • 4.配置pointcut
    <!-- ADVISOR =  POINTCUT + ADVICE -->
    <!-- 将ADVICE注入到POINTCUT位置,织入的过程 -->
    <bean id="logAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <!-- 指定advice-->
        <property name="advice" ref="logAdvice"></property>
        <!-- 指定匹配的方法-->
        <property name="mappedNames">
            <list>
                <value>login</value>
            </list>
        </property>
    </bean>
  • 配置代理
    <!-- 配置代理 -->
    <bean id="userSrv" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 目标类实例 -->
        <property name="target" ref="userSrvTarget"></property>
        <!-- 目标类接口列表 -->
        <property name="interfaces">
            <list>
                <value>org.example.UserSrv</value>
            </list>
        </property>
        <!-- 交叉业务逻辑 -->
        <property name="interceptorNames">
            <list>
                <value>logAdvisor</value>
            </list>
        </property>
    </bean>

Spring AOP 2.X (了解)

  • 基于命名空间的代理
  • 简化配置
  • 非侵入性,编写通知时不需要写任何的接口
  • 使用AspectJ表达式定义切点

四种增强类型

写法 说明 备注
public void XXX(JoinPoint joinPoint) 前置通知
public void XXX(JoinPoint joinPoint,Object o) 后置通知
public void XXX(JoinPoint joinPoint,Exception e) 异常通知
public Object XXX(ProceedingJoinPoint p) 环绕通知

实例

  • 1.需要增强的目标类实例
  • 2.配置增强类
  • 3.配置pointcut,并织入
    (匹配类中的所有方法)
<?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"
       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">

    <!-- 配置目标类实例 -->
    <bean id="userSrv" class="org.example.aop2.UserSrvImpl"></bean>

    <!-- 配置advice -->
    <bean id="logAdvice" class="org.example.aop2.LogAdvice"></bean>


    <!-- 配置pointcut并织入 -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="pc" expression="within(org.example.aop2.UserSrvImpl)"/>
        <!-- 织入 -->
        <aop:aspect ref="logAdvice">
            <!-- 将LogAdvice中的log方法以前置通知的方式织入到UserSrvImpl-->
            <aop:before method="preAdvice" pointcut-ref="pc"></aop:before>

            <aop:after-returning method="postAdvice" pointcut-ref="pc" returning="val"></aop:after-returning>
            <aop:after-throwing method="exceptionAdvice" pointcut-ref="pc" throwing="e"></aop:after-throwing>
            <aop:around method="processAdvice" pointcut-ref="pc" ></aop:around>
        </aop:aspect>
    </aop:config>

</beans>
public class LogAdvice {
    /**
     * 前置增强
     * @param joinPoint
     */
    public void preAdvice(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature(); //签名
        String methodName = signature.getName(); //方法名
        MethodSignature methodSignature = (MethodSignature)signature; // 转换为方法签名
        Method method = methodSignature.getMethod();
        Object[] args = joinPoint.getArgs(); //方法参数
        Object target = joinPoint.getThis(); // 目标对象
        Object target1 = joinPoint.getTarget();

        System.out.println();

        System.out.println("signature: "+signature);
        System.out.println("methodName: "+methodName);
        System.out.println("method: "+method);
        System.out.println("args: "+ Arrays.toString(args));
        System.out.println("target: "+target);
        System.out.println("target1: "+target1);

        System.out.println();
        System.out.println();
    }

    /**
     * 后置通知
     * @param joinPoint
     * @param val
     */
    public void postAdvice(JoinPoint joinPoint,Object val){
        System.out.println("postAdvice----------- val:"+val);

    }

    /**
     * 异常通知
     * @param joinPoint
     * @param e
     */
    public void exceptionAdvice(JoinPoint joinPoint,Exception e){
        System.out.println("exceptionAdvice ----- e: "+e);
    }

    /**
     * 环绕通知
     * @param proceedingJoinPoint
     */
    public Object processAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("processAdvice pre __");
        Object o = proceedingJoinPoint.proceed();
        System.out.println("processAdvice post __");
        return o;
    }
}

within

  • 1.通配符*
<aop:pointcut id="pc" expression="within(org.example.aop2.*Impl)"/>
  • 2.作用在类中所有方法

execution (常用)

  • 1.通配符*和..
  • 2.更加精确execution(返回值类型包名.类名.方法名(参数类型))
        <!-- 第1个*:返回值类型任意  -->
        <!-- 第2个*:匹配aop1包下所有的类 -->
        <!-- 第3个*:匹配类下的所有方法  -->
        <!-- (..): 参数任意  -->
        <aop:pointcut id="pc" expression="execution(* org.example.aop1.*.*(..))"/>

Annotation,基于注解的配置(推荐)

扫描包,数据装配

IOC注解

扫描包

package org.example.ioc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;

//@Component: 默认bean的id为类名首字母小写-springBean,可通过(value = "beanId")修改名称
@Component(value = "SpringBean")
public class SpringBean {
    @Value("${name}")
    private String name;
    @Value("${gender}")
    private String gender;
    @Value("${tag}")
    private String tag;

    /**
     * @Autowired:Spring提供的自动注解 默认按照byType, 如有多个同类型则按照byName
     * 方式1:结合@Qualifier("springBean")则按照byName
     * 方式2:@resource,JavaEE提供
     */
    @Autowired
    private OtherBean otherBean;


    @Autowired
    @Qualifier("list")
    private List list;

    @Resource
    private Integer[] array;

    @Resource
    private Map<String, String> map;

    @Resource
    private Set<String> set;

    @Resource
    private Properties properties;

<!-- 扫描包 -->
    <context:component-scan base-package="org.example.ioc"></context:component-scan>
    <context:component-scan base-package="org.example.srv.impl"></context:component-scan>
    <context:component-scan base-package="org.example.dao.impl"></context:component-scan>

数据装配

 <context:property-placeholder location="classpath:user.properties"></context:property-placeholder>
 <!-- 数据装配 -->
    <util:list id="list">
        <value>"1"</value>
        <value>"2"</value>
    </util:list>

    <util:list id="array">
        <value>1</value>
        <value>1</value>
        <value>2</value>
    </util:list>

    <util:map id="map">
        <entry key="1" value="1"></entry>
        <entry key="2" value="2"></entry>
    </util:map>

    <util:set id="set">
        <value>2</value>
        <value>2</value>
        <value>3</value>
    </util:set>
    
    <util:properties id="properties">
        <prop key="key1">1</prop>
    </util:properties>

生命周期

   @PostConstruct
    public  void init(){
        System.out.println("init()");
        System.out.println();
    }

    @PreDestroy
    public  void destory(){
        System.out.println("init()");
    }

bean实力话时机

  • 类注解
@Lazy(value = false)//单例情况下默认为value = false,可value = true设置,非懒加载

作用域

  • 类注解
@Scope(value = "singleton") //value = "prototype(非单例)—/singleton(单例)"

AOP注解

  • xml
    <context:component-scan base-package="org.example.srv.impl"></context:component-scan>
    <context:component-scan base-package="org.example.dao.impl"></context:component-scan>
    <context:component-scan base-package="org.example.aop"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • java

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAdvice {

    @Pointcut("execution(String org.example.*.impl.*.*(..))")
    private void pointCut(){
        System.out.println("pointCut()");
    }

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println();
        System.out.println("Aspect-before --");
        System.out.println();
    }

    @AfterReturning(pointcut = "pointCut()",returning="returnVal")
    public void after(JoinPoint joinPoint, String returnVal){
        System.out.println();
        System.out.println("Aspect-after");
    }

    @AfterThrowing(pointcut = "pointCut()",throwing = "e")
    public void throwing(JoinPoint joinPoint, Exception e) {
        System.out.println();
        System.out.println("Aspect-throwing");
        System.out.println(joinPoint.getSignature().getName() + " Exception e: " + e);
        System.out.println();
    }

    @Around(("pointCut()"))
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println();
        System.out.println("Aspect-around before---");
        Object val = null;
        try {
            val = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            System.out.println("catch");
            throwable.printStackTrace();
        }
        System.out.println("Aspect-around after---");
        System.out.println();
        return val;
    }

}

源码地址上传地址

菜鸟一枚,共同学习,共同进步,如有不对之处,请大神指点一二

https://github.com/zhaimiya/springframework

推荐阅读