1.1_Spring-AOP

1. AOP

1.1 什么是AOP面向切面编程

1.1.1 什么是AOP?

Aspect Oriented Program 面向切面编程
在不改变原有逻辑上增加额外的功能,比如解决系统层面的问题,或者增加新的功能

AOP思想把功能分两个部分,分离系统中的各种关注点

  • 核心关注点:业务的主要功能
  • 横切关注点:非核心、额外增加的功能

好处
○ 减少代码侵入,解耦
○ 可以统一处理横切逻辑
○ 方便添加和删除横切逻辑

使用场景:权限控制 缓存 日志处理 事务控制

1.2 核心概念

横切、通知、连接点、切入点、切面

横切关注点
● 对哪些方法进行拦截,拦截后怎么处理,这些就叫横切关注点
● 比如 权限认证、日志、事物

通知 Advice
● 在特定的切入点上执行的增强处理,有5种通知,后面讲解
● 做啥? 比如你需要记录日志,控制事务 ,提前编写好通用的模块,需要的地方直接调用

连接点 JointPoint
● 要用通知的地方,业务流程在运行过程中需要插入切面的具体位置,
● 一般是方法的调用前后,全部方法都可以是连接点
● 只是概念,没啥特殊

切入点 Pointcut
● 不能全部方法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那几个你想要的方法
● 在程序中主要体现为书写切入点表达式(通过通配、正则表达式)过滤出特定的一组 JointPoint连接点
● 过滤出相应的 Advice 将要发生的joinpoint地方

切面 Aspect
● 通常是一个类,里面定义 切入点+通知 , 定义在什么地方; 什么时间点、做什么事情
● 通知 advice指明了时间和做的事情(前置、后置等)
● 切入点 pointcut 指定在什么地方干这个事情
● web接口设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面,对象和对象,方法和方法之间都是一个个切面
● 目标 target,真正的业务逻辑,可以在目标类不知情的条件下,增加新的功能到目标类的链路上
● 织入 Weaving,把切面(某个类)应用到目标函数的过程称为织入
● AOP代理,AOP框架创建的对象,代理就是目标对象的加强,Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理

1.3 通知Advice类型讲解

@Before前置通知:
在执行目标方法之前运行

@After后置通知:
在目标方法运行结束之后

@AfterReturning返回通知:
在目标方法正常返回值后运行

@AfterThrowing异常通知:
在目标方法出现异常后运行

@Around环绕通知
在目标方法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint,需要手动执行 joinPoint.procced()

1.4 举个通俗的栗子-AOP面向切面编程

常见例子

用户下单

  • 核心关注点:创建订单
  • 横切关注点:记录日志、控制事务

用户观看付费视频

  • 核心关注点:获取播放地址
  • 横切关注点:记录日志、权限认证

在这里插入图片描述

//目标类 VideoOrderService; 里面每个方法都是连接点,;切入点是CUD类型的方法,R读取的不作为切入点
//CRDU全称:增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)

class VideoOrderService{
    //新增订单
    addOrder(){ }
    //查询订单
   findOrderById(){}
   //删除订单
   delOrder(){}
   //更新订单
   updateOrder(){}   
}


//权限切面类 = 切入点+通知 
class PermissionAspects{
  
  //切入点  定义了什么地方
    @Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
  public void pointCut(){}
  
  
  //before 通知 表示在目标方法执行前切入, 并指定在哪个方法前切入
  //什么时候,做什么事情
  @Before("pointCut()")
  public void permissionCheck(){
    
    System.out.println("在 xxx 之前执行权限校验");
  }
  ....
}

//日志切面类 = 切入点+通知 
class LogAspect{
  
  //切入点  定义了什么地方
    @Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
  public void pointCut(){}
  
  
  //after 通知 表示在目标方法执行后切入, 并指定在哪个方法前切入
  //什么时候,做什么事情
  @After("pointCut()")
  public void logStart(){
    
    System.out.println("在 xxx 之后记录日志");
  }
  ....
}

1.5 AOP切入点表达式

        访问修饰符          返回值类型(必填)     包和类                    方法(必填)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)  throws-pattern?) 

除了返回类型、方法名和参数外,其它项都是可选的 (修饰符基本都是省略不写)

  @Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")

1.5.1 常见匹配语法

* 匹配任何数量字符 单个;
..匹配任何数量字符,可以多个,在类型模式中匹配任何数量子包;在方法参数模式中匹配任何数量参数

() 匹配一个不接受任何参数的方法
(..) 匹配一个接受任意数量参数的方法
(*) 匹配了一个接受一个任何类型的参数的方法
(*,Integer) 匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是Integer类型

2. AOP动态代理

2.1 AOP的实现策略之JDK动态代理

JDK动态代理的原理是对指定的业务类实现的接口再生成一个实现类,在其中的业务方法中来实现代理

定义一个java.lang.reflect.InvocationHandler接口的实现类,重写invoke方法

//Object proxy:被代理的对象  
//Method method:要调用的方法  
//Object[] args:方法调用时所需要参数  
public interface InvocationHandler {  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;  
}  
public class JdkProxy implements InvocationHandler {

    //目标类
    private Object targetObject;
    //获取代理对象
    public Object newProxyInstance(Object targetObject){
        this. targetObject = targetObject;

        //绑定关系,也就是和具体的哪个实现类关联
        return  Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(),this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) {
        Object result = null;
        try{
            System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 begin");
            result = method.invoke(targetObject,args);
            System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 end");
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }
}

2.2 AOP的实现策略之CGLib动态代理

CgLib动态代理的原理是对指定的业务类生成一个子类,并覆盖其中的业务方法来实现代理

public class CglibProxy  implements MethodInterceptor {

    //目标类
    private Object targetObject;

    //绑定关系
    public Object newProxyInstance(Object targetObject){
        this.targetObject = targetObject;
        Enhancer enhancer = new Enhancer();
        //设置代理类的父类(目标类)
        enhancer.setSuperclass(this.targetObject.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建子类(代理对象)
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;
        try{
            System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
            result = methodProxy.invokeSuper(o,args);
            System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }
}

2.3 CGLib动态代理和JDK动态代理总结

● 两种动态代理的区别:
○ JDK动态代理:要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以用CGLib动态代理
○ CGLib动态代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展
○ JDK动态代理是自带的,CGlib需要引入第三方包
○ CGLib动态代理基于继承来实现代理,所以无法对final类、private方法和static方法实现代理

● Spring AOP中的代理使用的默认策略:
○ 如果目标对象实现了接口,则默认采用JDK动态代理
○ 如果目标对象没有实现接口,则采用CgLib进行动态代理
○ 如果目标对象实现了接扣,程序里面依旧可以指定使用CGlib动态代理

3. AOP示例

● 需求分析:针对Videoservice接口实现日志打印
● 三个核心包
○ spring-aop:AOP核心功能,例如代理工厂
○ aspectjweaver:简单理解,支持切入点表达式
○ aspectjrt:简单理解,支持aop相关注解

● 定义service接口和实现类
● 定义横切关注点
● 引入相关包

<dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>1.6.11</version>
</dependency>

*● maven仓库改为阿里云

<repositories>
        <repository>
            <id>maven-ali</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
                <checksumPolicy>fail</checksumPolicy>
            </snapshots>

        </repository>
 </repositories>

● 添加schema

<?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"
        xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

</beans>

● 配置bean和aop

<bean id="timeHandler" class="net.xdclass.sp.aop.TimeHandler"/>
<bean id="videoService" class="net.xdclass.sp.service.VideoServiceImpl"/>

<!--aop配置-->
<aop:config>

  <!--横切关注点-->
  <!-- 切面 -->
  <aop:aspect id="timeAspect" ref="timeHandler">

    <!--定义切入点表达式-->
    <!--<aop:pointcut id="allMethodLogPointCut" expression="execution(* net.xdclass.sp.service.VideoService.sav*(..))"/>-->
    <aop:pointcut id="allMethodLogPointCut" expression="execution(* net.xdclass.sp.service.VideoService.*(..))"/>

    <!--配置前置通知和后置通知-->
    <aop:before method="printBefore" pointcut-ref="allMethodLogPointCut"/>
    <aop:after method="printAfter" pointcut-ref="allMethodLogPointCut"/>
  </aop:aspect>


</aop:config> 

4. XML配置到注解配置

4.1 开启注解配置和包扫描

public static void main(String[] args) {


     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

    //扫描指定的包,包括子包
    context.scan("net.xdclass");


    //里面完成初始化操作,核心方法
    context.refresh();


    VideoService videoService = (VideoService) context.getBean("videoService");
    videoService.findById(2);


    Video video = (Video) context.getBean("video");
    video.init();
    
}

4.2 常用注解和xml对比

  • bean定义
    ○ xml方式:
    ○ 注解方式:@Component 通用组件 细分: @Controller (用于web层) @Service (用于service层) @Repository (用于dao仓库层)

  • bean取名
    ○ xml方式:通过id或者name
    ○ 注解方式:@Component("XXXX")

  • bean注入
    ○ xml方式:通过<property …>
    ○ 注解方式:类型注入@Autowired 名称注入@Autowired+@Qualifier 以及@Resource

  • bean生命周期
    ○ xml方式:init-methoddestroy-method
    ○ 注解方式:@PostConstruct初始化、@PreDestroy销毁

  • bean作用范围
    ○ xml方式:scope属性
    ○ 注解方式:@scope注解

5. @Configuration@Bean注解定义第三方bean

@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文)

@bean注解:用于告诉方法产生一个Bean对象,然后这个Bean对象交给Spring管理,Spring将会将这个Bean对象放在自己的IOC容器中

注意点: SpringIOC容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建

@Configuration
public class AppConfig {

    //使用@bean注解,表明这个bean交个spring 进行管理
    // 如果没有指定名称,默认采用 方法名 + 第一个字母小写 作为bean的名称
    @Bean(name = "videoOrderName",initMethod = "init",destroyMethod = "destroy")
    @Scope
    public VideoOrder videoOrder(){
        return new VideoOrder();
    }
}

6. 自动映射配置文件@PropertySource@Value注解

@PropertySource,指定加载配置文件(即配置文件映射到实体类)
● 使用@Value映射到具体的java属性

@Configuration
@PropertySource(value = {"classpath:config.properties"})
public class CustomConfig {

    @Value("${server.host}")
    private String host;

    @Value("${server.port}")
    private int port;

    //setter getter
}

7. 注解配置AOP面向切面编程

7.1 Spring AOP注解的基础准备

  • 声明切面类 @Aspect(切面): 通常是一个类,里面可以定义切入点和通知
  • 配置切入点和通知 @Pointcut @Around ...
@Component
//告诉spring,这个一个切面类,里面可以定义切入点和通知
@Aspect
public class LogAdvice {


    //切入点表达式
    @Pointcut("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
    public void aspect(){

    }

    //前置通知
    @Before("aspect()")
    public void beforeLog(JoinPoint joinPoint){
        System.out.println("LogAdvice  beforeLog");
    }


    //后置通知
    @After("aspect()")
    public void afterLog(JoinPoint joinPoint){
        System.out.println("LogAdvice  afterLog");
    }

	 /**
	     * 环绕通知
	     * @param joinPoint
	     */
	@Around("aspect()")
	public void around(JoinPoint joinPoint){
	
	    Object target = joinPoint.getTarget().getClass().getName();
	    System.out.println("调用者="+target);
	
	    //目标方法签名
	    System.out.println("调用方法="+joinPoint.getSignature());
	
	    //通过joinPoint获取参数
	    Object [] args = joinPoint.getArgs();
	    System.out.println("参数="+args[0]);
	
	
	    long start = System.currentTimeMillis();
	    System.out.println("环绕通知 环绕前=========");
	
	    //执行连接点的方法
	    try {
	        ((ProceedingJoinPoint)joinPoint).proceed();
	    } catch (Throwable throwable) {
	        throwable.printStackTrace();
	    }
	
	    long end = System.currentTimeMillis();
	    System.out.println("环绕通知 环绕后=========");
	
	    System.out.println("调用方法总耗时 time = " + (end - start) +" ms");
	}

}

7.2 开启Spring AOP注解和扫描

@Configuration
@ComponentScan("net.xdclass")
@EnableAspectJAutoProxy  //开启了spring对aspect的支持
public class AnnotationAppConfig {

}

public static void main(String[] args) {

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);

    VideoService videoService = (VideoService) context.getBean("videoService");

    videoService.findById(2);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值