1. AOP的简单实战

1.1 Service类

  1. package com.xixi.service.impl;
  2. import com.xixi.dao.IUserDao;
  3. import com.xixi.enitity.User;
  4. import com.xixi.service.IUserService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Service;
  7. @Service
  8. public class UserServiceImpl implements IUserService{
  9. @Autowired
  10. IUserDao userDao;
  11. @Override
  12. public User select(Integer id) throws Exception {
  13. System.out.println("查询user");
  14. return userDao.select(id);
  15. }
  16. @Override
  17. public void addUser(User user) throws Exception {
  18. System.out.println("新增user");
  19. userDao.addUser(user);
  20. }
  21. @Override
  22. public void update(User user) throws Exception {
  23. System.out.println("更新user");
  24. userDao.update(user);
  25. }
  26. @Override
  27. public void delete(Integer id) throws Exception {
  28. System.out.println("删除user");
  29. userDao.delete(id);
  30. }
  31. }

1.2 代理类

package com.xixi.aspect;

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

@Component //注册为Spring的bean
@Aspect   //标记为切面
public class LogAspect {

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

    }
    //后置通知
    @After("execution(* com.xixi.service.impl.*.*(..))")
    public void after(){
        System.out.println("后置通知");

    }
    //后置异常通知
    @AfterThrowing("execution(* com.xixi.service.impl.*.*(..))")
    public void afterThrowing(){
        System.out.println("后置异常通知");
    }
    //后置返回通知
    @AfterReturning("execution(* com.xixi.service.impl.*.*(..))")
    public void afterReturning(){
        System.out.println("后置返回通知");
    }
    //环绕通知

}

1.3 配置文件

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

        <!--开启自动注入 -->
    <context:component-scan base-package="com.xixi" />

    <!-- 开启aop-->
    <aop:aspectj-autoproxy />
</beans>

1.4 测试类


import com.xixi.service.IUserService;
import com.xixi.service.impl.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {

    @Test
    public void test1(){
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
        for (String beanDefinitionName : ioc.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        System.out.println("==================");
        //当没有使用aop的情况下,:class:UserServiceImpl...
        //当使用了aop的时候,class com.sun.proxy.$Proxy20 jdk代理产生的一个动态代理类当被代理的类实现了接口,会默认使用JDK代理
        //如果代理的对象没有实现接口,代理类会被cglib代理。class com.xixi.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$e08dd1bd

        //getBean的时候要么用名称即(userServiceImpl)获取,或者用接口类来获取


        //UserService实现接口
//        IUserService bean = ioc.getBean(IUserService.class);
//        System.out.println(bean.getClass());
//        System.out.println(ioc.getBean("userServiceImpl"));

        //UserService不实现接口
        UserServiceImpl u = ioc.getBean(UserServiceImpl.class);
        UserServiceImpl u2 = (UserServiceImpl) ioc.getBean("userServiceImpl");
        System.out.println(u.getClass());
        System.out.println(u2.getClass());
        System.out.println(u == u2);

    }


}

1.5 总结

                //当没有使用aop的情况下,:class:UserServiceImpl...
        //当使用了aop的时候,class com.sun.proxy.$Proxy20 jdk代理产生的一个动态代理类当被代理的类实现了接口,会默认使用JDK代理
        //如果代理的对象没有实现接口,代理类会被cglib代理。class com.xixi.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$e08dd1bd
        //getBean的时候要么用名称即(userServiceImpl)获取,或者用接口类来获取

2.AOP切入点表达式

支持切点标识符
Spring AOP支持使用以下AspectJ切点标识符(PCD),用于切点表达式:

  • execution: 用于匹配方法执行连接点。 这是使用Spring AOP时使用的主要切点标识符。 可以匹配到方法级别 ,细粒度
  • within: 只能匹配类这级,只能指定类, 类下面的某个具体的方法无法指定, 粗粒度
  • this: 匹配实现了某个接口:this(com.xyz.service.AccountService)
  • target: 限制匹配到连接点(使用Spring AOP时方法的执行),其中目标对象(正在代理的应用程序对象)是给定类型的实例。
  • args: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中变量是给定类型的实例。 AOP) where the arguments are instances of the given types.
  • @target: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注解。
  • @args: 限制匹配连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注解。
  • @within: 限制与具有给定注解的类型中的连接点匹配(使用Spring AOP时在具有给定注解的类型中声明的方法的执行)。
  • @annotation:限制匹配连接点(在Spring AOP中执行的方法具有给定的注解)。

image.png

访问修饰符:可不写 可以匹配任何一个访问修饰符
返回值:如果是jdk自带类型可以不用写完整限定名,如果是自定义类型需要写上完整限定名,如果被切入的方法返回值不一样可以使用 代表所有的方法值都能匹配
包名:cn.
== cn.xixi == cn.任意名字 但是只能匹配一级 比如 cn.xixi.service就无法匹配
如果要cn.xixi.service ==>cn.xixi.service , cn.xixi. ==>cn.xixi.service.impl就无法匹配
cn.xixi..
==>cn.xixi.service.impl 可以匹配
类名: 可以写,代表任何名字的类名。 也可以模糊匹配 ServiceImpl==> UserServiceImpl ==>RoleServiceImpl
方法名:可以写,代表任何方法。 也可以模糊匹配 add==> useradd ==>roleadd
参数:如果是jdk自带类型可以不用写完整限定名,如果是自定义类型需要写上完整限定名。 如果需要匹配任意参数 可以写:..

2.1 within表达式

1.within表达式
通过类名进行匹配 粗粒度的切入点表达式
within(包名.类名)
则这个类中的所有的连接点都会被表达式识别,成为切入点。

<aop:pointcut expression="within(cn.xixi.service.UserServiceImpl)"


在within表达式中可以使用*号匹配符,匹配指定包下所有的类,注意,只匹配当前包,不包括当前包的子孙包。

<aop:pointcut expression="within(cn.xixi.service.*)"


在within表达式中也可以用*号匹配符,匹配包

<aop:pointcut expression="within(cn.xixi.*.*)"

在within表达式中也可以用..*号匹配符,匹配指定包下及其子孙包下的所有的类

<aop:pointcut expression="within(cn.xixi..*)"

2.2execution()表达式

细粒度的切入点表达式,可以以方法为单位定义切入点规则

细粒度的切入点表达式,可以以方法为单位定义切入点规则
语法:execution(返回值类型 包名.类名.方法名(参数类型,参数类型…))

例子1:

<aop:pointcut expression="execution(void cn.xixi.service.UserServiceImpl.addUser(java.lang.String))" id="pc1"/>

该切入点规则表示,切出指定包下指定类下指定名称指定参数指定返回值的方法。
例子2:

 <aop:pointcut expression="execution(* cn.xixi.service.*.query())" id="pc1"/>

该切入点规则表示,切出指定包下所有的类中的query方法,要求无参,但返回值类型不限。
例子3:

 <aop:pointcut expression="execution(* cn.xixi.service..*.query())" id="pc1"/>

该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法,要求无参,但返回值类型不限。
例子4:

<aop:pointcut expression="execution(* cn.xixi.service..*.query(int,java.lang.String))" id="pc1"/>


该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法,要求参数为int java.langString类型,但返回值类型不限。
例子5:

 <aop:pointcut expression="execution(* cn.xixi.service..*.query(..))" id="pc1"/>

该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法,参数数量及类型不限,返回值类型不限。
例子6:

 <aop:pointcut expression="execution(* cn.xixi.service..*.*(..))" id="pc1"/>

该切入点规则表示,切出指定包及其子孙包下所有的类中的任意方法,参数数量及类型不限,返回值类型不限。这种写法等价于within表达式的功能。
例子7:

 <aop:pointcut expression="execution(* cn.xixi.service..*.del*(..))" id="pc1"/>

2.3.合并切点表达式

您可以使用 &&, || 和 !等符号进行合并操作。也可以通过名字来指向切点表达式。

//&&:两个表达式同时
execution( public int cn.tulingxueyuan.inter.MyCalculator.*(..)) && execution(* *.*(int,int) )
//||:任意满足一个表达式即可
execution( public int cn.tulingxueyuan.inter.MyCalculator.*(..)) && execution(* *.*(int,int) )
//!:只要不是这个位置都可以进行切入
//&&:两个表达式同时
execution( public int cn.tulingxueyuan.inter.MyCalculator.*(..))

3.通知方法的执行顺序

在之前的代码中大家一直对通知的执行顺序有疑问,其实执行的结果并没有错,大家需要注意:
image.png
更新说明:https://github.com/spring-projects/spring-framewor… #25186链接:https://github.com/spring-projects/spring-framewor…

4.获取方法的详细信息

在上面的案例中,我们并没有获取Method的详细信息,例如方法名、参数列表等信息,想要获取的话其实非常简单,只需要添加JoinPoint参数即可。

package cn.tulingxueyuan.util;

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

import java.util.Arrays;

@Component
@Aspect
public class LogUtil {

    @Before("execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))")
    public static void start(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法开始执行,参数是:"+ Arrays.asList(args));
    }

    @AfterReturning("execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))")
    public static void stop(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行完成,结果是:");

    }

    @AfterThrowing("execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))")
    public static void logException(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法出现异常:");
    }

    @After("execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))")
    public static void end(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行结束了......");
    }
}

刚刚只是获取了方法的信息,但是如果需要获取结果,还需要添加另外一个方法参数,并且告诉spring使用哪个参数来进行结果接收

 @AfterReturning(value = "execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))",
                    returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行完成,结果是:"+result);

    }

也可以通过相同的方式来获取异常的信息

@AfterThrowing(value = "execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))",throwing = "exception")
    public static void logException(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法出现异常:"+exception);
    }

5.spring对通过方法的要求

spring对于通知方法的要求并不是很高,你可以任意改变方法的返回值和方法的访问修饰符,但是唯一不能修改的就是方法的参数,会出现参数绑定的错误,原因在于通知方法是spring利用反射调用的,每次方法调用得确定这个方法的参数的值。

 @After("execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))")
    private int end(JoinPoint joinPoint,String aa){ 
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行结束了......");
        return 0;
    }

6.表达式的抽取

如果在实际使用过程中,多个方法的表达式是一致的话,那么可以考虑将切入点表达式抽取出来:
a、随便生命一个没有实现的返回void的空方法
b、给方法上标注@Potintcut注解

package cn.xixi.util;

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

import java.util.Arrays;

@Component
@Aspect
public class LogUtil {

    @Pointcut("execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))")
    public void myPoint(){}

    @Before("myPoint()")
    public static void start(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法开始执行,参数是:"+ Arrays.asList(args));
    }

    @AfterReturning(value = "myPoint()",returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行完成,结果是:"+result);

    }

    @AfterThrowing(value = "myPoint()",throwing = "exception")
    public static void logException(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法出现异常:"+exception.getMessage());
    }

    @After("myPoint()")
    private int end(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行结束了......");
        return 0;
    }
}

7.环绕通知的使用

package cn.tulingxueyuan.util;

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

import java.util.Arrays;

@Component
@Aspect
public class LogUtil {
    @Pointcut("execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))")
    public void myPoint(){}

    /**
     * 环绕通知是spring中功能最强大的通知
     * @param proceedingJoinPoint
     * @return
     */
    @Around("myPoint()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();
        String name = proceedingJoinPoint.getSignature().getName();
        Object proceed = null;
        try {
            System.out.println("环绕前置通知:"+name+"方法开始,参数是"+Arrays.asList(args));
            //利用反射调用目标方法,就是method.invoke()
            proceed = proceedingJoinPoint.proceed(args);
            System.out.println("环绕返回通知:"+name+"方法返回,返回值是"+proceed);
        } catch (Throwable e) {
            System.out.println("环绕异常通知"+name+"方法出现异常,异常信息是:"+e);
        }finally {
            System.out.println("环绕后置通知"+name+"方法结束");
        }
        return proceed;
    }
}

总结:环绕通知的执行顺序是优于普通通知的,具体的执行顺序如下:
环绕前置—>普通前置—>目标方法执行—>环绕正常结束/出现异常—>环绕后置—>普通后置—>普通返回或者异常。
但是需要注意的是,如果出现了异常,那么环绕通知会处理或者捕获异常,普通异常通知是接收不到的,因此最好的方式是在环绕异常通知中向外抛出异常。

异常特殊说明:由于使用反射调用方法捕捉到的异常ex.getMessage=null ; 需要通过ex.getCause() 这一点细节注意一下

public static void main(String[] args) throws Exception {
    try{
        Class<?> aClass = OrderController.class;
        Method add = aClass.getMethod("error");

        add.invoke(aClass.newInstance());
    }catch (Exception ex){
        ex.printStackTrace();
        System.out.println(ex.getCause().getMessage());
    }
}

8.Xml配置AOP

之前我们讲解了基于注解的AOP配置方式,下面我们开始讲一下基于xml的配置方式,虽然在现在的企业级开发中使用注解的方式比较多,但是你不能不会,因此需要简单的进行配置,注解配置快速简单,配置的方式共呢个完善。
1、将所有的注解都进行删除
2、添加配置文件

环绕通知执行完一定要将返回值返回,不然后置返回通知将无法获取到值。

package com.xixi.aspect;

import jdk.nashorn.internal.runtime.logging.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;

@Component //注册为Spring的bean
@Aspect   //标记为切面
public class LogAspect {

    //    @Pointcut("execution(* com.xixi.service.impl.*.*(..))")
//    public void pointcut()
//    {
//
//    }
    @Pointcut("execution(* com.xixi.service.impl.*.*(..))")
    public void pointcut() {

    }


//    前置通知
    @Before("pointcut() && @annotation(logger)")
    public void before(JoinPoint joinPoint, Logger logger) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("logger:" + logger.name());
//        System.out.println(joinPoint.getSignature().getDeclaringType());
        System.out.println(joinPoint.getArgs());
        System.out.println("methodName:" + methodName);
        System.out.println("前置通知");

    }

//    后置通知
    @After(value = "execution(* com.xixi.service.impl.*.*(..))")
    public void after() {
        System.out.println("后置通知");

    }

//    后置异常通知
    @AfterThrowing(value = "pointcut()",
            throwing = "ex")
    public void afterThrowing(Exception ex) {
        StringWriter sw = new StringWriter();
        ex.printStackTrace(new PrintWriter(sw, true));
        System.out.println("后置异常通知" + sw.getBuffer().toString());
    }

//    后置返回通知
    @AfterReturning(value = "execution(* com.xixi.service.impl.*.*(..))",returning = "res")
    public void afterReturning(Object res){
        System.out.println("后置返回通知:"+res);
    }

    //环绕通知
//    @Around("execution(* com.xixi.service.impl.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        System.out.println("环绕:前置通知:" + "name:" + name + ",参数:" + Arrays.asList(args));
        Object o = null;
        try {
            o = joinPoint.proceed();
            System.out.println("环绕:后置通知:" + "name:" + name + ",参数:" + Arrays.asList(args));

        } catch (Throwable throwable) {
            System.out.println("环绕:异常通知:" + "name:" + name + ",参数:" + Arrays.asList(args));
            throwable.printStackTrace();
        } finally {
            System.out.println("环绕:后置返回通知:" + "name:" + name + ",参数:" + Arrays.asList(args) + ",返回值:" + o);
        }
        return o;

    }


}