Spring—AOP(面向切面编程)

Spring—AOP(面向切面编程)

一、AOP是什么?
AOP(Aspect Oriented Programming)面向切面编程,是OOP的一种重要补充,也是Spring的另一个核心。
OOP是基于封装、继承、多态的编程思想,关注类之间纵向关系;AOP关注横向关系,能够为多个相互没有关系,又都需要某些共同功能的类,提供一些通用服务(如:日志、权限、缓存、事务等)。


二、AOP有什么用?
代码解耦,可以把与类的核心业务无关,又都需要的功能封装起来,让类只关注自己的核心业务,分离了系统的核心业务和非核心业务。


三、AOP的术语

术语描述
Aspect切面:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为切面。
Joinpoint连接点:被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
Pointcut切入点:对连接点进行拦截的定义。
Advice通知:所谓通知指的就是拦截到连接点后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
Target目标对象:代理的目标对象,将切面应用到目标对象并导致代理对象创建的过程。
Introduction/Weave引入/织入:在不修改代码的前提下,引入可以在运行期间为类动态地添加一些方法或字段。

通知Advice的类型
在这里插入图片描述


四、AOP配置版案例

  1. 引入依赖
		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
  1. 编写通知类
import org.aspectj.lang.ProceedingJoinPoint;

public class Main {
    public void start(){
        System.out.println("欢迎光临!");
    }
    public void shopping(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("导购员:请问需要帮忙吗?");
        joinPoint.proceed();
        System.out.println("好的,您自己随便看");
    }
    public void checkout(){
        System.out.println("这是您的账单");
    }
    public void end(){
        System.out.println("慢走不送,期待您的下次光临!");
    }
}
  1. 编写普通类
public class GoodsService {

    public void shopping() {
        System.out.println("顾客在购物");
    }
}
  1. 编写配置文件
<?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">

    <bean id="goodsService" class="com.hhx.springaop01.service.GoodsService"></bean>

<!--    配置包的扫描-->
    <context:component-scan base-package="com.hhx.springaop01"></context:component-scan>
<!--    配置通知类-->
    <bean id="main" class="com.hhx.springaop01.utils.Main"></bean>
    <aop:config>
<!--        配置切入点-->
        <aop:pointcut id="shopping" expression="execution(* com.hhx.springaop01.service.*Service.*(..))"/>
        <!--    配置切面 ref是通知类的bean-->
        <aop:aspect id="aspect" ref="main">
            <aop:before method="start" pointcut-ref="shopping"></aop:before>
            <aop:around method="shopping" pointcut-ref="shopping"></aop:around>
            <aop:after-returning method="checkout" pointcut-ref="shopping"></aop:after-returning>
            <aop:after method="end" pointcut-ref="shopping"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>
  1. 编写测试类
public class TestAOP01 {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-aop.xml");
        GoodsService goodsService = context.getBean(GoodsService.class);
        goodsService.shopping();
    }
}
  1. 测试结果
    在这里插入图片描述
    注:aop:pointcut的expression属性:通过表达式控制切面应用的范围
    语法:execution(访问修饰符 返回值 包名.类名.方法名(参数类型,参数类型…))
    通配符:① *代表任意长度的字符 ② … 代替子包或任意参数

五、AOP注解版案例

注解描述
@Aspect切面,配置到切面类上
@PointCut(“表达式”)配置切入点,加在方法上
@Before配置前置通知方法
@After配置后置通知方法
@Around配置环绕通知方法
@AfterReturning配置后置返回值通知方法
@AfterThrowing配置后置抛出异常通知方法

  1. 注入依赖
		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
  1. 创建配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * 配置类
 */
@EnableAspectJAutoProxy	
@ComponentScan(basePackages = "com.hhx.springaop02")	
@Configuration
public class AOPConfig {

}
  1. 配置切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 商店切面
 */

@Component
@Aspect
public class Main {
    @Pointcut("execution(* com.hhx.springaop02.*.*(..))")
    public void pointCut(){

    }

    @Before("pointCut()")
    public void start(){
        System.out.println("欢迎光临!");
    }

    @Around("pointCut()")
    public void shopping(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("导购员:请问需要帮忙吗?");
        joinPoint.proceed();
        System.out.println("好的,您自己随便看");
    }

    @AfterReturning("pointCut()")
    public void checkout(){
        System.out.println("这是您的账单");
    }

    @After("pointCut()")
    public void end(){
        System.out.println("慢走不送,期待您的下次光临!");
    }
}
  1. 普通类
import org.springframework.stereotype.Component;

@Component
public class GoodsService {

    public void shopping() {
        System.out.println("顾客在购物");
    }
}
  1. 测试
public class TestAOP02 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AOPConfig.class);
        GoodsService goodsService = context.getBean(GoodsService.class);
        goodsService.shopping();
    }
}
  1. 测试结果
    在这里插入图片描述
    从以上案例中注意到:
    配置版通知方法执行顺序为:before-around-afterreturning-after
    注解版通知方法执行顺序为:around-before-afterreturning-after-around

六、Log4J日志追踪案例

  1. 注入依赖
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
  1. 创建log4j.properties
# ROOTER
log4j.rootLogger=DEBUG,CONSOLE,FILE

# CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} %-5p %-20c %x %m%n

# FILE
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.File=logs.log
log4j.appender.FILE.MaxBackupIndex=20
log4j.appender.FILE.MaxFileSize=10MB
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} %-5p %-20c %x %m%n

# ERROR
log4j.appender.ERR = org.apache.log4j.DailyRollingFileAppender
log4j.appender.ERR.File =error.log
log4j.appender.ERR.Append = true
log4j.appender.ERR.Threshold = ERROR
log4j.appender.file.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.ERR.layout = org.apache.log4j.PatternLayout
log4j.appender.ERR.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n


##########################################################################################################


#Appender
#日志输出器,配置日志的输出级别、输出位置等,包括以下几类:
#ConsoleAppender: 		日志输出到控制台;
#FileAppender:		输出到文件;
#RollingFileAppender:	输出到文件,文件达到一定阈值时,自动备份日志文件;
#DailyRollingFileAppender:可定期备份日志文件,默认一天一个文件,也可设置为每分钟一个、每小时一个;
#WriterAppender:		可自定义日志输出位置。
#
#日志级别
#一般日志级别包括:ALL,DEBUG, INFO, WARN, ERROR,FATAL,OFF
#Log4J推荐使用:DEBUG, INFO, WARN, ERROR
#
#
#
#Layout:日志输出格式,Log4j提供的layout有以下几种:
#org.apache.log4j.HTMLLayout(以HTML表格形式布局),
#org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
#org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
#org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
#
#输出格式
#Log4J最常用的日志输出格式为:org.apache.log4j.PatternLayOut,其主要参数为:
#%n - 换行
#%m - 日志内容
#%p - 日志级别(FATAL, ERROR,WARN, INFO,DEBUG or custom)
#%r - 程序启动到现在的毫秒数
#%t - 当前线程名
#%d - 日期和时间, 一般使用格式 %d{yyyy-MM-dd HH:mm:ss, SSS}
#%l - 输出日志事件的发生位置, 同 %F%L%C%M
#%F - java 源文件名
#%L - java 源码行数
#%C - java 类名,%C{1} 输出最后一个元素
#%M - java 方法名
#
#Logger log  = Logger.getLogger(当前类.class);
#if(log.isDebugEnabled()){
#log.debug("...");
#}
#if(log.isInfoEnabled()){
#log.info("...");
#}
  1. 编写日志切面
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * Log4j日志输出切面
 */
@Aspect
@Component
public class Log4jAspect {

    //创建日志对象
    private Logger logger = Logger.getLogger(Log4jAspect.class);

    //给所有的类的所有方法加日志跟踪
    @Pointcut("execution(* com.hhx.springaop03.*.*(..))")
    public void logPointCut(){

    }

    //配置环绕通知
    @Around("logPointCut()")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable{
        //记录方法执行时间
        long start = System.currentTimeMillis();
        //打印方法名称
        if (logger.isDebugEnabled()){
            logger.debug("当前执行方法:"+joinPoint.getSignature().getName());
        }
        //打印参数
        Object[] args = joinPoint.getArgs();
        for (Object arg:args) {
            if (logger.isDebugEnabled()){
                logger.debug("参数:"+arg);
            }
        }
        logger.debug("开始执行方法……");
        //打印返回值
        Object result = joinPoint.proceed();
        logger.debug("结束执行方法……");
        if(logger.isDebugEnabled()){
            logger.debug("方法返回值:"+result);
        }
        //打印执行时间
        long end = System.currentTimeMillis();
        if (logger.isDebugEnabled()){
            logger.debug("方法执行时间:"+(end-start));
        }
        return result;
    }
    
}

  1. 测试
public class TestLog {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AOPConfig.class);
        //调用普通类方法
        GoodsService goodsService = context.getBean(GoodsService.class);
        goodsService.shopping();
    }
}
  1. 测试结果
    控制台输出:
    在这里插入图片描述
    日志文档(自动生成):
    在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值