Spring从入门到精通—基于Annotation配置和使用AOP

1、AspectJ 通知类型

AOP 联盟定义通知类型,AOP联盟的jar都是接口,必须要有实现类。 AspectJ通知类型只定义类型名称,以及方法格式,总共有6种; 1.brfore:前置通知(应用:各种校验) 在方法执行前执行,如果通知抛出异常,将不会执行方法 2.afterReturning:后置通知(应用:常规数据处理) 方法正常返回后执行,如果方法中抛出异常,通知将无法执行 3. around:环绕通知(应用:十分强大,可以做任何事) 方法执行前后分别执行,可阻止方法执行 4.afterThrowing:抛出异常通知(应用:包装异常信息) 方法抛出异常后执行,如果方法没有抛出异常,无法执行 5.after:最终通知(应用:清理现场) 方法执行完毕后执行,无论方法中是否出现异常都会执行(类似于finally代码块)

2、回顾基于XML配置AOP

在上一篇笔记*Spring从入门到精通—Spring AOP的XML配置和使用*中讲了如何使用xml的方式配置和使用Spring的AOP,这里我们再回顾一下:

首先编写一个service接口,模拟要处理的业务

package com.xzy.service;

public interface UserSeviceBase {
    /**
     * 增加用户
     */
    public void addUser();

    /**
     * 删除用户
     * @param id
     */
    public int deleteUser(int id);

    /**
     * 更新用户
     * @param id
     */
    public void updateUser(int id);

}

实现sevice接口:

package com.xzy.service;

public class UserSeviceImpl implements  UserSeviceBase {
    @Override
    public void addUser() {
        System.out.println("增加了1个用户");
    }

    @Override
    public int deleteUser(int id) {
       System.out.println("删除了id为"+id+"的用户");
       return id;
    }

    @Override
    public void updateUser(int id) {
      System.out.println("id为"+id+"的用户更新了");
    }
}

编写切面类:写一个方法before(),他是在目标方法执行需要增强的功能

package com.xzy.Aspect;

import org.aspectj.lang.JoinPoint;

public class MyAspect2 {

    /*
    *前置通知
    *JoinPoint:连接点
     */
    public void before(JoinPoint jp){
        System.out.println("前置通知........"+jp.getSignature().getName());   //得到方法的名字
    }
}

在XML中的配置如下:

ApplicationContext2.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置bean-->
    <bean id="userService" class="com.xzy.service.UserSeviceImpl"></bean>

    <!--配置切面类-->
    <bean id="aspect" class="com.xzy.Aspect.MyAspect2"></bean>

    <!--配置aop-->
    <aop:config>
        <!--指定切面-->
        <aop:aspect ref="aspect">
            <!--指定切入点-->
            <aop:pointcut id="poincut1" expression="execution(* com.xzy.service.*.*(..))"/>
            <!--前置通知-->
            <aop:before method="before" pointcut-ref="poincut1"/>
        </aop:aspect>
    </aop:config>
</beans>

测试:

package com.xzy;

import com.xzy.service.UserSeviceBase;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AppTest {

    @Test
    public void test(){
        ApplicationContext context=new ClassPathXmlApplicationContext("ApplicationContext2.xml");

        //从Spring容器中拿代理对象
        UserSeviceBase userSevice= (UserSeviceBase) context.getBean("userService");

        userSevice.deleteUser(23);

        userSevice.updateUser(3);
    }
}

测试结果:如下图所示,前置通知确实起作用了,在目标方法执行之前就执行了

下面我们在来测测<aop:advisor>中的其他通知方式

<aop:after-returning>

    在切面类中增加方法:afterReturning(JoinPoint jp,Object obj),其中第一个参数是连接点,第二个参数是目标方法运行后的返回值。要获得返回返回值需要在配置中设置returning="obj",就是把这个第二个参数的名字放进去,Spring就会把返回值注入。

package com.xzy.Aspect;

import org.aspectj.lang.JoinPoint;

public class MyAspect2 {

    /*
    *方法执行前的通知
     */
    public void before(JoinPoint jp){
        System.out.println("前置通知........"+jp.getSignature().getName());   //得到方法的名字
    }

      /*
      *方法返回后的通知
       */
    public void afterReturning(JoinPoint jp,Object obj){
        System.out.println("后置通知........"+jp.getSignature().getName());   //得到方法的名字
        System.out.println("方法的返回值是:"+obj);
        System.out.println("----------------------------------");
    }

}

配置新增的切面类方法:

  <!--配置bean-->
    <bean id="userService" class="com.xzy.service.UserSeviceImpl"></bean>

    <!--配置切面类-->
    <bean id="aspect" class="com.xzy.Aspect.MyAspect2"></bean>

    <!--配置aop-->
    <aop:config>
        <!--指定切面-->
        <aop:aspect ref="aspect">
            <!--指定切入点-->
            <aop:pointcut id="poincut1" expression="execution(* com.xzy.service.UserSeviceImpl.*(..))"/>
            <!--前置通知-->
            <aop:before method="before" pointcut-ref="poincut1"/>

            <!--后置通知-->
            <aop:after-returning method="afterReturning" pointcut-ref="poincut1" returning="obj"></aop:after-returning>

        </aop:aspect>
    </aop:config>

测试代码不变,测试结果如下:

#####<aop: around> around具有before和after-returning两者的功能,这里就不在重复测试了。所以一般使用了around就不在使用brfore和after-returning

<aop: after-throwing>、<aop: after>

切面类中增加方法afterThrowingafter


    /**
     * 抛出异常后通知
     * @param jp   连接点
     * @param e   异常
     */
    public void afterThrowing(JoinPoint jp,Throwable e){
        System.out.println("抛出异常通知....."+jp.getSignature().getName()+e.getMessage());
    }


    public void after(JoinPoint jp){
        System.out.println("最终通知......"+jp.getSignature().getName());
    }

配置xml

 <!--配置aop-->
    <aop:config>
        <!--指定切面-->
        <aop:aspect ref="aspect">
            <!--指定切入点-->
            <aop:pointcut id="poincut1" expression="execution(* com.xzy.service.UserSeviceImpl.*(..))"/>
       
            <!--环绕通知-->
            <aop:around method="around" pointcut-ref="poincut1"></aop:around>

            <!--异常通知:当目标方法发生异常后会执行 -->
          <aop:after-throwing method="afterThrowing" pointcut-ref="poincut1" throwing="e"/>

           <!--最终通知:无论方法有没有发生异常,都会执行-->
           <aop:after method="after" pointcut-ref="poincut1"/>

    </aop:config>

并在deleteUser方法中主动抛出异常:

    @Override
    public int deleteUser(int id) {
        System.out.println("删除了id为" + id + "的用户");
        throw  new RuntimeException(new Exception("自定义异常...."));
    }

测试结果如下:

如果去掉异常的测试结果如下:

2、基于Annotation配置AOP

既然使用注解配置,那就全部用注解,包括配置文件都用注解+Java类来实现 * 编写配置类Appconfig.java替代xml文件

package com.xzy;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration       //告诉spring这是配置文件
@EnableAspectJAutoProxy   //开启aop自动代理
@ComponentScan(basePackages = {"com.xzy"})    //告诉spring去哪里扫描注解
public class AppConfig {

   //这里头以后可以写各种配置,这个类的作用就和XML文件的作用一样
}

当然这段java代码可以用下面这段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:annotation-config/> 
  <!--告诉Spring去哪里扫描注解-->
  <context:component-scan base-package="com.xzy"></context:component-scan>
  <!--配置aop自动代理-->
  <aop:aspectj-autoproxy/>
     
</beans>

编写一个日志记录的切面类LoggerApsect.java 在切面类中可以使用如下几个注解来定制一个切面:

  • @Aspect:告诉Spring这是切面类
  • @Brfore:前置通知
  • @AfterRuning:返回后通知
  • @Around:环绕通知,是@Brfore和@AfterRuning<的结合,功能十分强大
  • @After-Throwing:抛出异常后的通知,没有异常不会执行
  • @After:最终通知,无论有没有异常一定会执行的
  • @PointCut:定义切点
具体用法如下:
package com.xzy.Aspect;

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

import java.util.Date;

/**
 * 一个记录各种操作的日志切面类
 */
@Component //告诉Spring你要把这个类给我实例化了
@Aspect    //告诉Spring这是一个切面类
public class LoggerAspect {

    //声明一个公共的切点:将com.xzy.service包下的所有以Impl结尾的方法作为切入点,切入点中可以没有任何代码实现,只是让他在形式上存在即可
    @Pointcut("execution(* com.xzy.service.*Impl.*(..))")
    public void pointcut() {
    }


    @Before("execution(* com.xzy.service.*Impl.*(..))")
    public void before(JoinPoint joinPoint) {
        System.out.println("before给" + joinPoint.getSignature().getName() + "方法作前日志.........." + new Date());
    }

    @AfterReturning(pointcut = "pointcut()", returning = "retValue")
    public void afterReturning(JoinPoint joinPoint, Object retValue) {
        System.out.println("afterReturning给" + joinPoint.getSignature().getName() + "方法作后日志.........." + new Date());
        System.out.println("方法的返回值是:" + retValue);
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("around给" + joinPoint.getSignature().getName() + "方法作前日志.........." + new Date());
        Object retValue = joinPoint.proceed();
        System.out.println("around给" + joinPoint.getSignature().getName() + "方法作前后志.........." + new Date());
        return retValue;
    }

    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("给" + joinPoint.getSignature().getName() + "方法作异常日志:" + new Date() + "抛出" + e.getMessage());
    }

    @After("pointcut()")
    public void afterAll(JoinPoint joinPoint) {
        System.out.println("给" + joinPoint.getSignature().getName() + "方法作最终日志.........." + new Date());
    }
}

测试类如下:

package com.xzy;

import com.xzy.service.UserSeviceBase;
import org.apache.log4j.Logger;
import org.junit.Test;
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(classes= {AppConfig.class})
public class AppTest {

    private static Logger log= Logger.getLogger(AppTest.class);

    @Autowired
    UserSeviceBase userSevice;

    @Test
    public void test3(){
        userSevice.deleteUser(12);
        userSevice.updateUser(34);
    }
}

测试结果:

现在去掉before、after-return以及在deleteUser中抛出一个异常:

留言区

还能输入500个字符