AOP:面向切片编程

AOP:面向切片编程

简介

AOP解决的问题:将核心业务代码与外围业务(日志记录、权限校验、异常处理、事务控制)代码分离出来,提高模块化,降低代码耦合度,使职责更单一。

AOP应用场景:

日志记录、权限校验、异常处理、事务控制等

相关概念

img

joinPoint:连接点。在spring中只支持方法连接点,连接点指的是可以使用advice(增强)的地方,例如一个类中有5个方法,那么这5个方法,那么这5个方法都可以是连接点。

pointcut:切点。可理解为实实在在的连接点,即切入advice(增强)的点。例如一个类中有5个方法,其中有3个方法(连接点)需要织入advice(增强),那么这3个需要织入advice的连接点就是切点。

advice:增强。实际中想要添加的功能,如日志、权限校验。

before:前置增强,目标方法执行前之前执行。

after:后置增强,目标方法执行后执行。

around:环绕增强,在目标方法执行时执行,可控制目标方法是否执行。

after throwing:异常增强,目标方法抛出异常时执行。

weaving:织入,即对方法的增强,将切面的代码织入(应用)到目标函数的过程。

introduction advice:引入增强。即对类的增强。

advisor:切面。由切点和增强相结合而成,定义增强应用到哪些切点上。

AOP的两种实现:基于代理的AOP实现&&Aspectj Spring AOP

Spring aop

基于代理(jdk动态代理、cglib动态代理)实现的aop

Spring aop使用了两种代理机制。一种是jdk动态代理,另一种是cglib动态代理。

Jdk动态代理只支持接口代理,cglib支持类的代理。

编程式实现

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springboot-practice-2021-9-5</artifactId>
<version>1.0-SNAPSHOT</version>
  
  
<properties>
    <spring.version>4.1.7.RELEASE</spring.version>
</properties>
  
<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.0</version>
    </dependency>
  
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>

</dependencies>
  
</project>
编程式

前置增强 before advice

前置增强:实现MethodBeforeAdvice接口,执行目标方法前执行before方法

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class UserBeforeAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("Before");
    }

}

后置增强 after advice

后置增强:实现AfterReturningAdvice接口,执行目标方法后执行afterReturning方法

import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class UserAfterAdvice implements AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("After");
    }
}

环绕增强 around advice

环绕增强:实现MethodInterceptor接口,执行目标方法前后执行invoke方法

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class UserAroundAdvice implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before");
        Object result = invocation.proceed();
        System.out.println("After");
        return result;
    }
}

用户服务接口类:

public interface UserService { 
    void queryAll();
}

用户服务接口实现类:

public class UserServiceImpl implements UserService {
    public void queryAll() {
        System.out.println("查询全部用户并返回");
    }
}

测试类

import com.liuermeng.aop.advice.UserAfterAdvice;
import com.liuermeng.aop.advice.UserAroundAdvice;
import com.liuermeng.aop.advice.UserBeforeAdvice;
import com.liuermeng.aop.service.IUserService;
import com.liuermeng.aop.service.impl.UserServiceImpl;
import org.springframework.aop.framework.ProxyFactory;

public class Test {
    public static void main(String[] args) {
        /**
         * 测试前置增强和后置增强
         */
        ProxyFactory proxyFactory = new ProxyFactory();//创建代理工厂
        proxyFactory.setTarget(new UserServiceImpl());//射入目标类对象
        proxyFactory.addAdvice(new UserBeforeAdvice());//添加前置增强
        proxyFactory.addAdvice(new UserAfterAdvice());//添加后置增强
        IUserService userService = (IUserService) proxyFactory.getProxy();//从代理工厂获取代理
        userService.queryAll();//调用代理的方法

        /**
         * 测试环绕增强
         */
        ProxyFactory proxyFactory2 = new ProxyFactory();//创建代理工厂
        proxyFactory2.setTarget(new UserServiceImpl());//射入目标类对象
        proxyFactory2.addAdvice(new UserAroundAdvice());//添加环绕增强
        IUserService userService2 = (IUserService) proxyFactory2.getProxy();//从代理工厂获取代理
        userService2.queryAll();//调用代理的方法

    }
}

结果

Before
查询全部用户并返回
After
Before
查询全部用户并返回
After
声明式(基于xml)
环绕增强 around advice

spring-aop.xml配置文件如下:

<?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: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/context
      http://www.springframework.org/schema/context/spring-context.xsd">

    <!--扫描指定包-->
    <context:component-scan base-package="com.liuermeng.aop"/>

    <!--配置代理-->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--需要代理的接口-->
        <property name="interfaces" value="com.liuermeng.aop.service.IUserService"/>
        <!--接口实现类-->
        <property name="target" ref="userServiceImpl"/>
        <!--拦截器名称(即增强类名称)-->
        <property name="interceptorNames">
            <list>
                <value>userAroundAdvice</value>
            </list>
        </property>

    </bean>

</beans>

其他的代码除了测试类以外与上面相同,另外在aroundAdvice类上面加上@Component标签

测试环绕增强代码:

import com.liuermeng.aop.service.IUserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {
        // 获取spring context
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop2.xml");
        // 从context中获取id为userServiceProxy的代理对象
        IUserService userServiceProxy = (IUserService) applicationContext.getBean("userServiceProxy");
        // 调用代理的方法
        userServiceProxy.queryAll();

    }
}

结果

Before
查询全部用户并返回
After
抛出增强 throws advice

抛出增强:实现ThrowsAdvice接口。当执行目标方法出现异常会执行抛出增强中的afterThrowing方法。

/**
 * 抛出增强
 */
@Component
public class UserThrowAdvice implements ThrowsAdvice {
 
    public void afterThrowing(Method method, Object[] args, Object target, Exception e) {
        System.out.println("Throw exception:");
        System.out.println("Target class name:" + target.getClass().getName());
        System.out.println("Method name: " + method.getName());
        System.out.println("Exception message:" + e.getMessage());
    }
}

.xml配置文件修改如下:

<?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: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/context
      http://www.springframework.org/schema/context/spring-context.xsd">

    <!--扫描指定包-->
    <context:component-scan base-package="com.liuermeng.aop"/>

    <!--配置代理-->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--需要代理的接口-->
        <property name="interfaces" value="com.liuermeng.aop.service.IUserService"/>
        <!--接口实现类-->
        <property name="target" ref="userServiceImpl"/>
        <!--拦截器名称(即增强类名称)-->
        <property name="interceptorNames">
            <list>
<!--                <value>userAroundAdvice</value>-->
                <value>userThrowAdvice</value>
            </list>
        </property>

    </bean>

</beans>

测试代码同上

结果

查询全部用户并返回
Throw exception:
Target class name:com.liuermeng.aop.service.impl.UserServiceImpl
Method name: queryAll
Exception message:Error哈哈哈哈
Exception in thread "main" java.lang.RuntimeException: Error哈哈哈哈
	at com.liuermeng.aop.service.impl.UserServiceImpl.queryAll(UserServiceImpl.java:10)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:125)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
	at com.sun.proxy.$Proxy6.queryAll(Unknown Source)
	at com.liuermeng.aop.Test.main(Test.java:15)
切面 advisor

advisor(切面)封装了advice(增强)和pointcut(切点)

在UserService接口中添加两个方法query、save.

UserService代码如下:

public interface IUserService { 

    void queryAll(); 

    void query(); 

    void save();

}

UserServiceImpl代码如下:

@Component

public class UserServiceImpl implements UserService { 

    @Override

    public void queryAll() {

        System.out.println("查询全部用户并返回");

    }
 

    @Override

    public void query() {

        System.out.println("根据条件查询用户");

    }
 

    @Override

    public void save() {

        System.out.println("新增用户");

    }
 

}

.xml配置文件如下:

<?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: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/context
      http://www.springframework.org/schema/context/spring-context.xsd">

    <!--扫描指定包-->
    <context:component-scan base-package="com.liuermeng.aop"/>

    <!-- 配置一个切面 -->
    <bean id="userServiceAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!-- 增强 -->
        <property name="advice" ref="userAroundAdvice"/>
        <!-- 切点 -->
        <property name="pattern" value="com.liuermeng.aop.service.impl.UserServiceImpl.query.*"/>

    </bean>

    <!--配置代理-->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--需要代理的接口-->
        <property name="interfaces" value="com.liuermeng.aop.service.IUserService"/>
        <!--接口实现类-->
        <property name="target" ref="userServiceImpl"/>
        <!-- qiemian -->
        <property name="interceptorNames" value="userServiceAdvisor"/>
        <!-- 代理目标类 -->
        <property name="proxyTargetClass" value="true"/>

    </bean>

</beans>

测试代码:

public class Test {
 

    public static void main(String[] args) {

        // 获取spring context

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");

        // 从context中获取id为userServiceProxy的代理对象

        UserService userServiceProxy = (UserService) applicationContext.getBean("userServiceProxy");

        // 调用代理的方法

        userServiceProxy.queryAll();

        userServiceProxy.query();

        userServiceProxy.save();

 

    }

}

结果如下:

Before
查询全部用户并返回
After
Before
根据条件查询用户
After
新增用户

可以看出UserService中的queryAll、query方法被拦截,执行这两个方法前后执行了环绕增强代码。而save方法没有被拦截。

基于Aspect Spring AOP开发

Spring AOP 与ApectJ 的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,Spring AOP 并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势,同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。在AspectJ 1.5后,引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种方式,因此Spring 2.0后便使用了与AspectJ一样的注解。请注意,Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。下面我们先通过一个简单的案例来演示Spring AOP的入门程序

基于注解

简单案例

pom中增加了Spring与junit相关的依赖

aspect注解定义的切面类UserAdvisor

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

@Component
@Aspect
public class UserAdvisor {
    /**
     * 环绕通知
     * @param joinPoint 可用于执行切点的类
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))")
    public Object arount(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知前....");
        Object obj= (Object) joinPoint.proceed();
        System.out.println("环绕通知后....");
        return obj;
    }

    /**
     * 前置通知
     */
    @Before("execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("前置通知");
    }

    /**
     * 后置通知
     * @param returnVal
     */
    @AfterReturning(value = "execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))",returning = "returnVal")
    public void adterReturn(Object returnVal){
        System.out.println("后置通知...."+returnVal);
    }

    /**
     * 最终通知
     */
    @After("execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("最终通知");
    }
    /**
     * 抛出通知
     * @param e
     */
    @AfterThrowing(value="execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))",throwing = "e")
    public void afterThrowable(Throwable e){
        System.out.println("出现异常:msg="+e.getMessage());
    }

}

IUserService

public interface IUserService {
    int queryAll();
    void query();

    void save();
}

实现类

import com.liuermeng.aop.service.IUserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements IUserService {
    public int queryAll() {
        System.out.println("查询全部用户并返回");
//        throw new RuntimeException("Error哈哈哈哈");
        return 666;
    }
    public void query() {

        System.out.println("根据条件查询用户");

    }

    public void save() {

        System.out.println("新增用户");

    }
}

配置文件.xml

<?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:context="http://www.springframework.org/schema/context"
       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/context
      http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.liuermeng.aop"/>
    <!-- 启动@aspectj的自动代理支持-->
    <aop:aspectj-autoproxy />
</beans>

测试类

import com.liuermeng.aop.service.IUserService;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= "classpath:aop3.xml")
public class Test {
    @Autowired
    IUserService userService ;
    @org.junit.Test
    public void test(){
        userService.queryAll();

    }
}

结果

环绕通知前....
前置通知
查询全部用户并返回
环绕通知后....
最终通知
后置通知....666

利用Spring2.0引入的aspect注解开发功能定义aspect类即UserAdvisor,在该aspect类中,编写了5种注解类型的通知函数,分别是前置通知@Before、后置通知@AfterReturning、环绕通知@Around、异常通知@AfterThrowing、最终通知@After,这5种通知与AspectJ的通知类型几乎是一样的,并在注解通知上使用execution关键字定义的切点表达式,即指明该通知要应用的目标函数,当只有一个execution参数时,value属性可以省略,当含两个以上的参数,value必须注明,如存在返回值时。当然除了把切点表达式直接传递给通知注解类型外,还可以使用@pointcut来定义切点匹配表达式,这个与AspectJ使用关键字pointcut是一样的,后面分析。目标类和aspect类定义完成后,最后需要在xml配置文件中进行配置,注意,使用Spring AOP 的aspectJ功能时,需要使用以下代码启动aspect的注解功能支持:

<aop:aspectj-autoproxy />
定义切入点表达式

在上面的案例中是采用直接在注解中使用“execution”定义表达式作为值传递给通知类型的,如下:

 /**
     * 前置通知
     */
    @Before("execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("前置通知");
    }

除了上述方式外,还可采用与ApectJ中使用pointcut关键字类似的方式定义切入点表达式如下,使用@Pointcut注解:

/**
 * 使用Pointcut定义切点
 */
@Pointcut("execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))")
public void myPointcut(){}
 /**
  * 前置通知
  */
@Before("myPointcut()")
public void before() {
    System.out.println("前置通知");
}
切入点指示符

为了方法通知应用到相应过滤的目标方法上,SpringAOP提供了匹配表达式,这些表达式也叫切入点指示符,在前面的案例中,它们已多次出现。

通配符
在定义匹配表达式时,通配符几乎随处可见,如*、.. 、+ ,它们的含义如下:

.. :匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包

//任意返回值,任意名称,任意参数的公共方法
execution(public * *(..))
//匹配com.zejian.dao包及其子包中所有类中的所有方法
within(com.zejian.dao..*)

+ :匹配给定类的任意子类

//匹配实现了DaoUser接口的所有子类的方法
within(com.zejian.dao.DaoUser+)

* :匹配任意数量的字符

//匹配com.zejian.service包及其子包中所有类的所有方法
within(com.zejian.service..*)
//匹配以set开头,参数为int类型,任意返回值的方法
execution(* set*(int))

类型签名表达式
为了方便类型(如接口、类名、包名)过滤方法,Spring AOP 提供了within关键字。其语法格式如下:

within(<type name>)

type name 则使用包名或者类名替换即可,来点案例吧。

//匹配com.zejian.dao包及其子包中所有类中的所有方法
@Pointcut("within(com.zejian.dao..*)")

//匹配UserDaoImpl类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl)")

//匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl+)")

//匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.zejian.dao.UserDao+)")

方法签名表达式
如果想根据方法签名进行过滤,关键字execution可以帮到我们,语法表达式如下

//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))

对于给定的作用域、返回值类型、完全限定类名以及参数匹配的方法将会应用切点函数指定的通知,这里给出模型案例:

//匹配UserDaoImpl类中的所有方法
@Pointcut("execution(* com.zejian.dao.UserDaoImpl.*(..))")

//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(..))")

//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.zejian.dao.UserDaoImpl.*(..))")

//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(int , ..))")

其他指示符
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;

//匹配名称中带有后缀Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}

this :用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配

//匹配了任意实现了UserDao接口的代理对象的方法进行过滤
@Pointcut("this(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut2(){}

target :用于匹配当前目标对象类型的执行方法;

//匹配了任意实现了UserDao接口的目标对象的方法进行过滤
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut3(){}

@within:用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行;

//匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("@within(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}

@annotation(com.zejian.spring.MarkerMethodAnnotation) : 根据所应用的注解进行方法过滤

//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}

ok~,关于表达式指示符就介绍到这,我们主要关心前面几个常用的即可,不常用过印象即可。这里最后说明一点,切点指示符可以使用运算符语法进行表达式的混编,如and、or、not(或者&&、||、!),如下一个简单例子:

//匹配了任意实现了UserDao接口的目标对象的方法并且该接口不在com.zejian.dao包及其子包下
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao) !within(com.zejian.dao..*)")
private void myPointcut6(){}
//匹配了任意实现了UserDao接口的目标对象的方法并且该方法名称为addUser
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)&&execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut7(){}
通知函数以及传递参数

在Spring中与AspectJ一样,通知主要分5种类型,分别是前置通知、后置通知、异常通知、最终通知以及环绕通知,下面分别介绍。

  • 前置通知@Before

    前置通知通过@Before注解进行标注,并可直接传入切点表达式的值,该通知在目标函数执行前执行,注意JoinPoint,是Spring提供的静态变量,通过joinPoint 参数,可以获取目标对象的信息,如类名称,方法参数,方法名称等,该参数是可选的。

/**
 * 前置通知
 */
@Before("myPointcut()")
public void before(JoinPoint joinPoint) {
    System.out.println("前置通知");
}
  • 后置通知@AfterReturning

    通过@AfterReturning注解进行标注,该函数在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同。请注意,在任何通知中这些参数都是可选的,需要使用时直接填写即可,不需要使用时,可以完成不用声明出来。如下

/**
* 后置通知,不需要参数时可以不提供
*/
@AfterReturning("myPointcut()")
public void AfterReturning(){
   System.out.println("我是后置通知...");
}
/**
* 后置通知
* returnVal,切点方法执行后的返回值
*/
@AfterReturning(value="myPointcut()",returning = "returnVal")
public void AfterReturning(JoinPoint joinPoint,Object returnVal){
   System.out.println("我是后置通知...returnVal+"+returnVal);
}
  • 异常通知 @AfterThrowing

    该通知只有在异常时才会被触发,并由throwing来声明一个接收异常信息的变量,同样异常通知也用于Joinpoint参数,需要时加上即可,如下:

/**
* 抛出通知
* @param e 抛出异常的信息
*/
@AfterThrowing(value="myPointcut()",throwing = "e")
public void afterThrowable(Throwable e){
  System.out.println("出现异常:msg="+e.getMessage());
}

  • 最终通知 @After

    该通知有点类似于finally代码块,只要应用了无论什么情况下都会执行。

/**
 * 无论什么情况下都会执行的方法
 * joinPoint 参数
 */
@After("myPointcut()")
public void after(JoinPoint joinPoint) {
    System.out.println("最终通知....");
}
  • 环绕通知@Around

    环绕通知既可以在目标方法前执行也可在目标方法之后执行,更重要的是环绕通知可以控制目标方法是否执行,但即使如此,我们应该尽量以最简单的方式满足需求,在仅需在目标方法前执行时,应该采用前置通知而非环绕通知。案例代码如下第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来执行目标函数,proceed()的返回值就是环绕通知的返回值。同样的,ProceedingJoinPoint对象也是可以获取目标对象的信息,如类名称,方法参数,方法名称等等。

@Around("myPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("我是环绕通知前....");
    //执行目标函数
    Object obj= (Object) joinPoint.proceed();
    System.out.println("我是环绕通知后....");
    return obj;
}
通知传递参数

在Spring AOP中,除了execution和bean指示符不能传递参数给通知方法,其他指示符都可以将匹配的方法相应参数或对象自动传递给通知方法。获取到匹配的方法参数后通过”argNames”属性指定参数名。如下,需要注意的是args(指示符)、argNames的参数名与before()方法中参数名 必须保持一致即param。

@Before(value="args(param)", argNames="param") //明确指定了    
public void before(int param) {    
    System.out.println("param:" + param);    
}  

当然也可以直接使用args指示符不带argNames声明参数,如下:

@Before("execution(public * com.zejian..*.addUser(..)) && args(userId,..)")  
public void before(int userId) {  
    //调用addUser的方法时如果与addUser的参数匹配则会传递进来会传递进来
    System.out.println("userId:" + userId);  
}  

args(userId,..)该表达式会保证只匹配那些至少接收一个参数而且传入的类型必须与userId一致的方法,记住传递的参数可以简单类型或者对象,而且只有参数和目标方法也匹配时才有会有值传递进来。

Aspect优先级

在不同的切面中,如果有多个通知需要在同一个切点函数指定的过滤目标方法上执行,那些在目标方法前执行(”进入”)的通知函数,最高优先级的通知将会先执行,在执行在目标方法后执行(“退出”)的通知函数,最高优先级会最后执行。而对于在同一个切面定义的通知函数将会根据在类中的声明顺序执行。如下:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AspectOne {
    /**
     * 使用Pointcut定义切点
     */
    @Pointcut("execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))")
    public void myPointcut(){}
    /**
     * 前置通知
     */
    @Before("myPointcut()")
    public void beforeone(JoinPoint joinPoint) {
        System.out.println("前置通知1......");
    }
    @Before("myPointcut()")
    public void beforetwo(JoinPoint joinPoint) {
        System.out.println("前置通知2......");
    }

    /**
     * 后置通知
     * @param
     */
    @AfterReturning(value = "myPointcut()")
    public void adterReturnthree(){
        System.out.println("后置通知3....");
    }
    @AfterReturning(value = "myPointcut()")
    public void adterReturnfour(){
        System.out.println("后置通知4....");
    }
}

结果

前置通知1......
前置通知2......
查询全部用户并返回
后置通知4....
后置通知3....

如果在不同的切面中定义多个通知响应同一个切点,进入时则优先级高的切面类中的通知函数优先执行,退出时则最后执行,如下定义AspectOne类和AspectTwo类并实现org.springframework.core.Ordered 接口,该接口用于控制切面类的优先级,同时重写getOrder方法,定制返回值,返回值(int 类型)越小优先级越大。其中AspectOne返回值为0,AspectTwo的返回值为3,显然AspectOne优先级高于AspectTwo。

package com.liuermeng.aop.advisor;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AspectOne implements Ordered {
    /**
     * 使用Pointcut定义切点
     */
    @Pointcut("execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))")
    public void myPointcut(){}
    /**
     * 前置通知
     */
    @Before("myPointcut()")
    public void beforeone(JoinPoint joinPoint) {
        System.out.println("AspectOne前置通知1......");
    }
    @Before("myPointcut()")
    public void beforetwo(JoinPoint joinPoint) {
        System.out.println("AspectOne前置通知2......");
    }

    /**
     * 后置通知
     * @param
     */
    @AfterReturning(value = "myPointcut()")
    public void adterReturnthree(){
        System.out.println("AspectOne后置通知3....");
    }
    @AfterReturning(value = "myPointcut()")
    public void adterReturnfour(){
        System.out.println("AspectOne后置通知4....");
    }

    public int getOrder() {
        return 0;
    }
}
package com.liuermeng.aop.advisor;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AspectTow {
    /**
     * 使用Pointcut定义切点
     */
    @Pointcut("execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))")
    public void myPointcut(){}
    /**
     * 前置通知
     */
    @Before("myPointcut()")
    public void beforeone(JoinPoint joinPoint) {
        System.out.println("AspectTwo前置通知1......");
    }
    @Before("myPointcut()")
    public void beforetwo(JoinPoint joinPoint) {
        System.out.println("AspectTwo前置通知2......");
    }

    /**
     * 后置通知
     * @param
     */
    @AfterReturning(value = "myPointcut()")
    public void adterReturnthree(){
        System.out.println("AspectTwo后置通知3....");
    }
    @AfterReturning(value = "myPointcut()")
    public void adterReturnfour(){
        System.out.println("AspectTwo后置通知4....");
    }

    public int getOrder() {
        return 3;
    }
}

结果

AspectOne前置通知1......
AspectOne前置通知2......
AspectTwo前置通知1......
AspectTwo前置通知2......
查询全部用户并返回
AspectTwo后置通知4....
AspectTwo后置通知3....
AspectOne后置通知4....
AspectOne后置通知3....


基于XML

前面分析完基于注解支持的开发是日常应用中最常见的,即使如此我们还是有必要了解一下基于xml形式的Spring AOP开发,这里会以一个案例的形式对xml的开发形式进行简要分析,定义一个切面类

package com.liuermeng.aop.advisor;

import org.aspectj.lang.ProceedingJoinPoint;

public class XmlAspect {
    public void before(){
        System.out.println("MyAspectXML====前置通知");
    }

    public void afterReturn(Object returnVal){
        System.out.println("后置通知-->返回值:"+returnVal);
    }

    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("MyAspectXML=====环绕通知前");
        Object object= joinPoint.proceed();
        System.out.println("MyAspectXML=====环绕通知后");
        return object;
    }

    public void afterThrowing(Throwable throwable){
        System.out.println("MyAspectXML======异常通知:"+ throwable.getMessage());
    }

    public void after(){
        System.out.println("MyAspectXML=====最终通知..来了");
    }

}

.xml

<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 name="userService" class="com.liuermeng.aop.service.impl.UserServiceImpl"/>
    <!-- 定义切面 -->
    <bean name="xmlAspect" class="com.liuermeng.aop.advisor.XmlAspect"/>
    <!-- 配置AOP切面 -->
    <aop:config>
        <!-- 定义切点函数 -->
        <aop:pointcut id="pointCut" expression="execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))"/>
        <!-- 定义其他切点函数 -->
        <aop:pointcut id="pointCutSave" expression="execution(* com.liuermeng.aop.service.impl.UserServiceImpl.save(..))"/>
        <!-- 定义通知 order 定义优先级,值越小优先级越大-->
        <aop:aspect ref="xmlAspect" order="0">
            <!-- 定义通知
            method 指定通知方法名,必须与MyAspectXML中的相同
            pointcut 指定切点函数
            -->
            <aop:before method="before" pointcut-ref="pointCut"/>
            <!-- 后置通知  returning="returnVal" 定义返回值 必须与类中声明的名称一样-->
            <aop:after-returning method="afterReturn" pointcut-ref="pointCut"  returning="returnVal" />
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pointCut"  />
            <!--异常通知 throwing="throwable" 指定异常通知错误信息变量,必须与类中声明的名称一样-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="throwable"/>
            <!--
                 method : 通知的方法(最终通知)
                 pointcut-ref : 通知应用到的切点方法
                -->
            <aop:after method="after" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>

</beans>

Test

import com.liuermeng.aop.service.IUserService;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= "classpath:aop.xml")
public class Test {
    @Autowired
    IUserService userService ;
    @org.junit.Test
    public void test(){
        userService.queryAll();

    }
}

结果

MyAspectXML====前置通知
MyAspectXML=====环绕通知前
查询全部用户并返回
MyAspectXML=====最终通知..来了
MyAspectXML=====环绕通知后
后置通知-->返回值:666


需要注意的是最终通知和后置通知的位置

简单应用

模拟性能监控,计算方法执行耗时。

package com.liuermeng.aop.advisor;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
 * 性能监控切面
 */
@Aspect
@Component
public class MonitorAspect {
    @Around("execution(* com.liuermeng.aop.service.impl.UserServiceImpl.*(..))")
    public Object arount(ProceedingJoinPoint pjp) throws Throwable{
        // 目标类名称
        String clazzName = pjp.getTarget().getClass().getName();
        // 目标类方法名称
        String methodName = pjp.getSignature().getName();
        // 计时并调用目标函数
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long time = System.currentTimeMillis() - start;

        System.out.println("执行" + clazzName + "." + methodName + "方法耗时" + time + "毫秒");
        // 可在此处将监控信息存储
        return result;
    }
}
package com.liuermeng.aop.service;

public interface IUserService {
    int queryAll();
    void query();
    void save();
}
package com.liuermeng.aop.service.impl;

import com.liuermeng.aop.service.IUserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements IUserService {
    public int queryAll() {
        System.out.println("查询全部用户并返回");
//        throw new RuntimeException("Error哈哈哈哈");
        return 666;
    }
    public void query() {

        System.out.println("根据条件查询用户");

    }

    public void save() {

        System.out.println("新增用户");

    }
}

.xml

<?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:context="http://www.springframework.org/schema/context"
       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/context
      http://www.springframework.org/schema/context/spring-context.xsd
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 扫描指定包 -->
    <context:component-scan base-package="com.liuermeng.aop"/>
    <!--默认为false,使用JDK动态代理 设置为true,启用cglib动态代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

测试

package com.liuermeng.aop.test;

import com.liuermeng.aop.service.IUserService;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:aop.xml")
public class Test {
    @Autowired
    IUserService userService;//
    @org.junit.Test
    public void test(){
        userService.queryAll();
        userService.query();
        userService.save();
    }
}

注意:由于开启了cglib动态代理,在测试类中可以不注入IUserService,而选择直接注入UserServiceImpl,UserServiceImpl也可以不用实现接口IUserService

Spring AOP实现原理概要

前面的分析中,我们谈到Spring AOP的实现原理是基于动态织入的动态代理技术,而AspectJ则是静态织入,而动态代理技术又分为Java JDK动态代理和CGLIB动态代理,前者是基于反射技术的实现,后者是基于继承的机制实现,下面通过一个简单的例子来分析这两种技术的代码实现。

JDK动态代理

先看一个简单的例子,声明一个A类并实现ExInterface接口,利用JDK动态代理技术在execute()执行前后织入权限验证和日志记录,注意这里仅是演示代码并不代表实际应用。

//自定义的接口类,JDK动态代理的实现必须有对应的接口类
public interface ExInterface {
    void execute();
}
//A类,实现了ExInterface接口类
public class A implements ExInterface {
    @Override
    public void execute() {
        System.out.println("执行A的execute方法...");
    }
}
package com.liuermeng.proxy.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//代理类的实现
public class JDKProxy implements InvocationHandler {
    /**
     * 要被代理的目标对象
     */
    private A target;
    public JDKProxy(A target){
        this.target=target;
    }
    /**
     * 创建代理类
     * @return
     */
    /**
     * 参数含义:
     * ClassLoader:和被代理对象使用相同的类加载器。
     * Interfaces:和被代理对象具有相同的行为。实现相同的接口。
     * InvocationHandler:如何代理。
     */
    public ExInterface createProxy(){
        return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    /**
     * 调用被代理类(目标对象)的任意方法都会触发invoke方法
     * @param proxy 代理类
     * @param method 被代理类的方法
     * @param args 被代理类的方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //过滤不需要该业务的方法
        if("execute".equals(method.getName())) {
            //调用前验证权限
            AuthCheck.authCheck();
            //调用目标对象的方法
            Object result = method.invoke(target, args);
            //记录日志数据
            Report.recordLog();
            return result;
        }else if("delete".equals(method.getName())){
            //.....
        }
        //如果不需要增强直接执行原方法
        return method.invoke(target,args);
    }

    //测试验证
    public static void main(String args[]){
        A a=new A();
        //创建JDK代理
        JDKProxy jdkProxy=new JDKProxy(a);
        //创建代理对象
        ExInterface proxy=jdkProxy.createProxy();
        //执行代理对象方法
        proxy.execute();
    }


}

结果

AuthCheck权限验证
执行A的execute方法...
Report记录日志并上传

在A的execute方法里面并没有调用任何权限和日志的代码,也没有直接操作a对象,相反地只是调用了proxy代理对象的方法,最终结果却是预期的,这就是动态代理技术,是不是跟Spring AOP似曾相识?实际上动态代理的底层是通过反射技术来实现,只要拿到A类的class文件和A类的实现接口,很自然就可以生成相同接口的代理类并调用a对象的方法了,关于底层反射技术的实现,暂且不过多讨论,请注意实现java的动态代理是有先决条件的,该条件是目标对象必须带接口,如A类的接口是ExInterface,通过ExInterface接口动态代理技术便可以创建与A类类型相同的代理对象,如下代码演示了创建并调用时利用多态生成的proxy对象:

A a=new A();
 //创建JDK代理类
 JDKProxy jdkProxy=new JDKProxy(a);
 //创建代理对象,代理对象也实现了ExInterface,通过Proxy实现
 ExInterface proxy=jdkProxy.createProxy();
 //执行代理对象方法
 proxy.execute();

代理对象的创建是通过Proxy类达到的,Proxy类由Java JDK提供,利用Proxy#newProxyInstance方法便可以动态生成代理对象(proxy),底层通过反射实现的,该方法需要3个参数

/**

* @param loader 类加载器,一般传递目标对象(A类即被代理的对象)的类加载器
* @param interfaces 目标对象(A)的实现接口
* @param h 回调处理句柄(后面会分析到)
  */
  public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

创建代理类proxy的代码如下:

public ExInterface createProxy(){
   return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

到此并没结束,因为有接口还是远远不够,代理类(Demo中的JDKProxy)还需要实现InvocationHandler接口,也是由JDK提供,代理类必须实现的并重写invoke方法,完全可以把InvocationHandler看成一个回调函数(Callback),Proxy方法创建代理对象proxy后,当调用execute方法(代理对象也实现ExInterface)时,将会回调InvocationHandler#invoke方法,因此我们可以在invoke方法中来控制被代理对象(目标对象)的方法执行,从而在该方法前后动态增加其他需要执行的业务,Demo中的代码很好体现了这点:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //过滤不需要该业务的方法
    if("execute".equals(method.getName())) {
        //调用前验证权限(动态添加其他要执行业务)
        AuthCheck.authCheck();

      //调用目标对象的方法(执行A对象即被代理对象的execute方法)
      Object result = method.invoke(target, args);

      //记录日志数据(动态添加其他要执行业务)
      Report.recordLog();

      return result;
}eles if("delete".equals(method.getName())){
 //.....
 	return method.invoke(target, args);
}
//如果不需要增强直接执行原方法
	return method.invoke(target,args);
}

invoke方法有三个参数:

  • Object proxy :生成的代理对象
  • Method method:目标对象的方法,通过反射调用
  • Object[] args:目标对象方法的参数

这就是Java JDK动态代理的代码实现过程,小结一下,运用JDK动态代理,被代理类(目标对象,如A类),必须已有实现接口如(ExInterface),因为JDK提供的Proxy类将通过目标对象的类加载器ClassLoader和Interface,以及句柄(Callback)创建与A类拥有相同接口的代理对象proxy,该代理对象将拥有接口ExInterface中的所有方法,同时代理类必须实现一个类似回调函数的InvocationHandler接口并重写该接口中的invoke方法,当调用proxy的每个方法(如案例中的proxy#execute())时,invoke方法将被调用,利用该特性,可以在invoke方法中对目标对象(被代理对象如A)方法执行的前后动态添加其他外围业务操作,此时无需触及目标对象的任何代码,也就实现了外围业务的操作与目标对象(被代理对象如A)完全解耦合的目的。当然缺点也很明显需要拥有接口,这也就有了后来的CGLIB动态代理了

CGLIB动态代理

通过CGLIB动态代理实现上述功能并不要求目标对象拥有接口类,实际上CGLIB动态代理是通过继承的方式实现的,因此可以减少没必要的接口,下面直接通过简单案例协助理解(CGLIB是一个开源项目,github网址是:https://github.com/cglib/cglib)。

先引入maven依赖

<dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>3.2.0</version>
</dependency>
//被代理的类即目标对象
public class A {
    public void execute(){
        System.out.println("执行A的execute方法...");
    }
}
package cglib1;


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

//代理类
public class CGLibProxy implements MethodInterceptor {

    /**
     * 被代理的目标类
     */
    private A target;

    public CGLibProxy(A target) {
        super();
        this.target = target;
    }

    /**
     * 创建代理对象
     * @return
     */
    public A createProxy(){
        // 使用CGLIB生成代理:
        // 1.声明增强类实例,用于生产代理类
        Enhancer enhancer = new Enhancer();
        // 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
        enhancer.setSuperclass(target.getClass());
        // 3.设置回调函数,即一个方法拦截
        enhancer.setCallback(this);
        // 4.创建代理:
        return (A) enhancer.create();
    }

    /**
     * 回调函数
     * @param proxy 代理对象
     * @param method 委托类方法
     * @param args 方法参数
     * @param methodProxy 每个被代理的方法都对应一个MethodProxy对象,
     *                    methodProxy.invokeSuper方法最终调用委托类(目标类)的原始方法
     * @return
     * @throws Throwable
     */

    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //过滤不需要该业务的方法
        if("execute".equals(method.getName())) {
            //调用前验证权限(动态添加其他要执行业务)
            AuthCheck.authCheck();

            //调用目标对象的方法(执行A对象即被代理对象的execute方法)
            Object result = methodProxy.invokeSuper(proxy, args);

            //记录日志数据(动态添加其他要执行业务)
            Report.recordLog();

            return result;
        }else if("delete".equals(method.getName())){
            //.....
            return methodProxy.invokeSuper(proxy, args);
        }
        //如果不需要增强直接执行原方法
        return methodProxy.invokeSuper(proxy, args);

    }



    public static void main(String[] args) {
        A a=new A();
        CGLibProxy proxy=new CGLibProxy(a);
        A proxy1 = proxy.createProxy();
        proxy1.execute();
    }
}

结果

AuthCheck权限验证
执行A的execute方法...
Report记录日志并上传

从代码看被代理的类无需接口即可实现动态代理,而CGLibProxy代理类需要实现一个方法拦截器接口MethodInterceptor并重写intercept方法,类似JDK动态代理的InvocationHandler接口,也是理解为回调函数,同理每次调用代理对象的方法时,intercept方法都会被调用,利用该方法便可以在运行时对方法执行前后进行动态增强。关于代理对象创建则通过Enhancer类来设置的,Enhancer是一个用于产生代理对象的类,作用类似JDK的Proxy类,因为CGLib底层是通过继承实现的动态代理,因此需要传递目标对象(如A)的Class,同时需要设置一个回调函数对调用方法进行拦截并进行相应处理,最后通过create()创建目标对象(如A)的代理对象,运行结果与前面的JDK动态代理效果相同。

public A createProxy(){
    // 1.声明增强类实例,用于生产代理类
    Enhancer enhancer = new Enhancer();
    // 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
    enhancer.setSuperclass(target.getClass());
    // 3.设置回调函数,即一个方法拦截
    enhancer.setCallback(this);
    // 4.创建代理:
    return (A) enhancer.create();
  }

关于JDK代理技术 和 CGLIB代理技术到这就讲完了,我们也应该明白 Spring AOP 确实是通过 CGLIB或者JDK代理 来动态地生成代理对象,这个代理对象指的就是 AOP 代理累,而 AOP 代理类的方法则通过在目标对象的切入点动态地织入增强处理,从而完成了对目标方法的增强。这里并没有非常深入去分析这两种技术,更倾向于向大家演示Spring AOP 底层实现的最简化的模型代码,Spring AOP内部已都实现了这两种技术,Spring AOP 在使用时机上也进行自动化调整,当有接口时会自动选择JDK动态代理技术,如果没有则选择CGLIB技术,当然Spring AOP的底层实现并没有这么简单,为更简便生成代理对象,Spring AOP 内部实现了一个专注于生成代理对象的工厂类,这样就避免了大量的手动编码,这点也是十分人性化的,但最核心的还是动态代理技术。从性能上来说,Spring AOP 虽然无需特殊编译器协助,但性能上并不优于AspectJ的静态织入,这点了解一下即可。最后给出Spring AOP简化的原理模型如下图。
img

posted @ 2021-09-06 17:10  liumeng哈哈哈  阅读(503)  评论(0编辑  收藏  举报