spring中AOP

spring中AOP,第1张

spring中AOP(面向切面编程)

面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是spring框架中的一个重要内容,是函数式编程中的一种衍生范程.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率.

AOP主要作用:在不修改原有代码的条件下,对方法进行扩展

IOC-DI和AOP之间的区别:
  1. IOC-DI:利用第三方容器将对象统一管理.利用依赖注入为属性赋值,解决了对象与对象之间的耦合性的问题.(解决对象间的耦合问题)
    升华:springMVC框架(对象)/Mybatis框架(对象)类似的框架都可以交给spring容器管理.spring可以以一种统一的方式管理其它第三方的框架,使得调用浑然一体,耦合低,解决了框架和框架之间耦合性问题.
  2. AOP 名称为面向切面编程,使得业务逻辑各部分之间的耦合度降低(解决业务逻辑间的耦合问题)
动态代理 动态代理作用:

使用代理机制,可以有效降低代码的耦合性,将公共的代码/重复的代码,写到代理机制中,通过代理机制调用目标方法,使得真实的业务被调用,通过代理机制,可以有效的降低业务的耦合性.

代理的特点:
使用代理的对象和使用目标的对象看起来是一模一样的

动态代理分类(重点) 动态代理-JDK动态代理
  1. JDK动态代理是JDK源码提供的,无需导入额外的jar包

  2. JDK动态代理对象要求实现和被代理者相同的接口(代理对象和目标对象拥有相同数等且相同的方法,保证用户使用体验一样)—必须有接口

  3. JDK动态代理创建速度快,运行时稍慢

创建代理对象工具API
  1. 动态代理创建代理对象的API Proxy.newProxyInstance(loader,interfaces,h)

  2. 动态代理的参数几个/分别是谁 1.类加载器 2.接口数组 3.InvocationHandler处理器

  3. 代理对象调用方法时,为了扩展方法内容,调用处理器方法~~~

JDK动态代理示例:

1.定义业务类
业务接口

package com.jt.demo1.service;

public interface UserService {
    void addUser();
}

业务类:

package com.jt.demo1.service;

import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("新增用户成功!!!");
    }
}

2.JDK动态代理

//当前类是工具API,目的获取代理对象
public class JDKProxy {

    /**
     * public static Object newProxyInstance(ClassLoader loader,
     *                                           Class[] interfaces,
     *                                           InvocationHandler h)
     * newProxyInstance方法参数说明:
     * ClassLoader loader,类加载器,将class加载到java运行机制中
     * Class[] interfaces,被代理者的接口数组,java可以多实现接口
     * InvocationHandler h,将代理对象扩展的内容写到处理器中
     */
    public static Object getProxy(Object target){
        //1.获取目标对象的类加载器
        ClassLoader loader = target.getClass().getClassLoader();
        //2.获取接口数组
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //创建代理对象
        /*
        InvocationHandler headler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("事务开始");
                //调用  让目标方法执行  target:目标对象!!!其它的固定写法
                Object result = method.invoke(target, args);//让目标方法执行
                System.out.println("事务结束");
                return result;
            }
        };
        return Proxy.newProxyInstance(loader, interfaces,headler);
        */
        
        
        return Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("事务开始");
                //调用  让目标方法执行  target:目标对象!!!其它的固定写法
                Object result = method.invoke(target, args);//让目标方法执行
                System.out.println("事务结束");
                return result;
            }
        });
    }

    /*
    //获取InvocationHandler对象
    public InvocationHandler getInvocationHandler(Object target){
        //当代理执行业务 *** 作时,通过InvocationHandler进行业务的扩展
        return new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("事务开始");
                //调用  让目标方法执行  target:目标对象!!!其它的固定写法
                Object result = method.invoke(target, args);//让目标方法执行
                System.out.println("事务结束");
                return result;
            }
        };
    }
     */

}

3.配置类

package com.jt.demo1.config;

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

@Configuration
@ComponentScan("com.jt.demo1")
public class SpringConfig {
}

4.测试类

public class TestTX {
    public static void main(String[] args) {
        //创建容器
        ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
        
        //1.获取目标对象
        UserService target = app.getBean(UserService.class);
        
        //2.获取代理对象,JDK动态代理和目标对象必须实现同一接口,保持一模一样
        UserService proxy = (UserService) JDKProxy.getProxy(target);
        
        proxy.addUser();
    }
}
CGLIB动态代理示例:

1.CGLIB动态代理需要导入额外的jar包,才能使用

2.CGLIB被代理者有没有接口都可以,但是CGLIB代理对象是目标对象的子类(继承)

3.CGLIB动态代理创建速度慢,运行时快

关于动态代理总结:

为什么使用动态代理:实现了业务层的解耦

规则:将公共的代码/重复的代码,写到代理对象中,业务层只专注于自己的业务即可~~

AOP介绍

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

总结: Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展.

AOP专业术语

1).连接点: 用户可以被扩展的方法
2).切入点: 用户实际扩展的方法
3).通知: 扩展方法的具体实现
4).切面: 将通知应用到切入点的过程

AOP面向切面编程入门案例

1.引入aop的jar包,配置pom.xml文件,让maven导包

<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-aopartifactId>
dependency>

2.定义service层的接口和实现类
接口:

package com.jt.demo2.service;

public interface UserService {
    void addUser();
}

接口的实现类:

package com.jt.demo2.service;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("新增用户成功!!!");
    }
}

3.创建aop包,在包下创建切面类:

package com.jt.demo2.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
 * AOP  面向切面编程  1年开发
 * 知识铺垫:切面=动态代理+方法扩展  后期被AOP API封装
 */
@Component
@Aspect  //标识当前类是一个切面
public class TxAspect {
    //编码:切面=切入点表达式+通知方法

    //切入点表达式:为谁创建代理对象!!!
    //bean后面的参为是实现类的key
    @Pointcut("bean(userServiceImpl)")
    public void pointCut(){
    }

    //通知方法:对原有方法的扩展
    @Before("pointCut()")
    public void before(){
        System.out.println("aop入门案例");
    }
}

4.创建配置类:

package com.jt.demo2.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.jt.demo2")
@EnableAspectJAutoProxy  //让AOP有效
public class SpringConfig {
}

5.创建测试类,查看结果:

package com.jt.demo2;
import com.jt.demo2.config.SpringConfig;
import com.jt.demo2.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAspect {
    public static void main(String[] args) {
        ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
        //看上去:是目标对象  AOP:内部已经封装动态代理规则
        //实际上:已经被AOP封装,是一个代理对象
        UserService userService = app.getBean(UserService.class);
        System.out.println(userService.getClass());
        userService.addUser();
    }
}

注解:
1.@Aspect 标识当前类是一个切面
2.@Pointcut 标识切入点表达式
3.@Before 通知注解
4.@EnableAspectJAutoProxy 让AOP注解生效(在配置标识)

切入点表达式

作用:配置AOP为谁创建代理对象

  1. bean标签
    语法:bean(“spring管理对象的id”)
@Pointcut("bean(userServiceImpl)")   //userServiceImpl这个spring容器管理对象的key  首字母小字

功能:只能匹配固定的类/对象 一个,只能匹配一个,粗粒度匹配

  1. within表达式
    语法:within(包名.类名)
@Pointcut("within(com.jt.demo2.service.UserServiceImpl)")  //匹配单个容器管理对象
@Pointcut("within(com.jt.demo2.service.*)")  //匹配com.jt.demo2.service包下容器管理的所有类对象

功能:可以匹配多个类,粗粒度匹配
说明:bean/within都是粗粒度的匹配,匹配的是类(类中还有方法/方法还可以重载!!!)

  1. execution表达式
    语法:execution(返回值类型 包名.方法名(参数列表))
//void 类型匹配,创建动态代理对象,扩展方法正常运行
@Pointcut("execution(void com.jt.demo2.service.UserServiceImpl.addUser())")  
//int 类型不匹配,创建动态代理对象失败,扩展方法无法运行
@Pointcut("execution(int com.jt.demo2.service.UserServiceImpl.addUser())") 
//要求:拦截service.包下的所有的类,任间方法,任意参数  任意返回值类型
@Pointcut("execution(* com.jt.demo2.service..*.*(..))")
//service下的所有类用..*(两个点加星),任意参数用两个点(..)
  1. @annotation自定义注解
  • 自定义注解:
package com.jt.demo2.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD) //声明注解用在方法上
@Retention(RetentionPolicy.RUNTIME) //声明注解的生命周期
public @interface TX {
}
  • 使用自定义的注解标识需要动态代理的方法:
package com.jt.demo2.service;
import com.jt.demo2.anno.TX;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
    @Override
    @TX
    public void addUser() {
        System.out.println("新增用户成功!!!");
    }
}
  • 为addUser方法创建动态代理(先使用自定义注解标识方法,然后@annotation标识为方法创建动态代理)
@Component
@Aspect  //标识当前类是一个切面
public class TxAspect {
    @Pointcut("@annotation(com.jt.demo2.anno.TX)")
    public void pointCut(){
    }

    //通知方法:对原有方法的扩展
    @Before("pointCut()")
    public void before(){
        System.out.println("aop入门案例");
    }
}
通知方法:(通知方法一定得和切入点表达式绑定一起使用)

作用:当用户满足了切入点表达式,则才会执行 扩展的方法.

通知的类型 前置通知 @Before 在目标方法执行前执行
 //通知方法:对原有方法的扩展
    @Before("pointCut()")   //前置通知,和切入点表达式的方法绑定
    public void before(){
        System.out.println("aop入门案例");
    }
后置通知 @AfterReturning 在目标方法执行后执行
@AfterReturning("pointCut()")   //后置通知,和切入点表达式的方法绑定
    public void afterReturning(){

        System.out.println("AOP后置通知");
    }

后置通知可以记录目标方法的返回值结果,例如:

 /*
    记录目标方法的返回值结果
    returning:后置通知获取返回值的属性
     */
    @AfterReturning(value="pointCut()",returning="result")   //在后置方法中获取方法的返回值,然后传到扩展方法使用
    public void afterReturning(Object result){
        System.out.println("AOP后置通知!!!");
        System.out.println("方法的返回值:"+result);
    }
异常通知(目标方法执行时抛出异常时,异常通知执行)
@AfterThrowing("pointCut()")
    public void afterThrowing(){
        System.out.println("抛出异常执行!!!");
    }

获取异常信息

   /*
    说明:如果程序执行抛出异常,则可以由异常通知进行记录
    throwing:抛出异常的属性
     */
    @AfterThrowing(value="pointCut()",throwing = "exception")
    public void afterThrowing(Exception exception){
        System.out.println("抛出异常执行!!!");
        //exception.printStackTrace();//打印详情异常信息,比较消耗内存日志信息
        System.out.println("异常信息:"+exception.getMessage());
    }
最终通知:不管目标方法如何,最终都会运行
 @After("pointCut()")
    public void after(){
        System.out.println("我是最终通知!!!");
    }
环绕通知

说明:

1.前四大通知类型不能控制目标方法的运行,所以在使用时一般记录程序的运行状态

2.在目标方法执行前后都要运行,只有环绕通知才可以控制目标方法是否运行,使用最多的通知方法

/*
    注意事项:环绕通知中必须添加参数,并且必须位于第一位
    
    joinPoint.proceed()方法的作用:
    1.如果有下一个通知,则执行下一个通知;
    2.如果没有下一个通知,则执行目标方法!
    @return
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知开始");
        Object result = joinPoint.proceed(); //目标方法运行
        System.out.println("环绕通知结束");
        return result;
    }
关于连接点joinPoint的用法

说明:当用户调用方法时,如果该方法被切入点表达式匹配了,则spring会为其对象创建代理,则将这个方法称为连接点
连接点的作用:通过joinPoint对象可以获取当前方法的所有参数,可以供用户记录

通过连接点joinPoint获取目标对象的信息案例:
  1. 自定义注解
@Target(ElementType.METHOD) //声明注解用在方法上
@Retention(RetentionPolicy.RUNTIME) //声明注解的生命周期
public @interface TX {
}
  1. 定义接口和实现类以及使用注解用在需要创建动态代理的方法上
    接口:
package com.jt.demo2.service;

public interface UserService {
    void addUser();
}

接口实现类:

package com.jt.demo2.service;
import com.jt.demo2.anno.TX;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
    @Override
    @TX
    public void addUser() {
        System.out.println("新增用户成功!!!");
    }
}
  1. 创建AOP切片类
@Component //切片类使用通用的Component注解
@Aspect  //标识当前类是一个切面
public class TxAspect {
    @Pointcut("@annotation(com.jt.demo2.anno.TX)")
    public void pointCut(){
        
    }

    //通知方法:对原有方法的扩展
    @Before("pointCut()")   //前置通知,和切入点表达式的方法绑定
    public void before(JoinPoint joinPoint){
        System.out.println("aop前置通知!!!");
        Class<?> targetClass = joinPoint.getTarget().getClass();//获取目标对象的类型
        System.out.println("目标对象的类型"+targetClass);
        String targetName = joinPoint.getSignature().getDeclaringTypeName();//获取目标对象的名称
        System.out.println("目标对象的名称:"+targetName);
        String methodName = joinPoint.getSignature().getName();//获取目标方法的名称
        System.out.println("目标方法名称:"+methodName);
        Object[] args = joinPoint.getArgs();//获取参数名称
        System.out.println("参数名称:"+ Arrays.toString(args));
    }
}

  1. 创建配置类
package com.jt.demo2.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.jt.demo2")
@EnableAspectJAutoProxy  //让AOP有效
public class SpringConfig {
}
  1. 测试类
package com.jt.demo2;
import com.jt.demo2.config.SpringConfig;
import com.jt.demo2.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAspect {
    public static void main(String[] args) {
        ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
        //看上去:是目标对象  AOP:内部已经封装动态代理规则
        //实际上:已经被AOP封装,是一个代理对象
        UserService userService = app.getBean(UserService.class);
        System.out.println(userService.getClass());
        userService.addUser();
    }
}

关于joinPoint和ProceedingJoinPoint区别

说明:

1.ProceedingJoinPoint只适用于环绕通知,国灰只有环绕通知,才能控制目标方法的运行

2.JoinPoint适用于其它的四大通知类型,可以用来记录运行的数据

3.ProceedingJoinPoint中有特殊的方法proceed();

4.如果使用"JoinPoint" 则必须位于参数的第一位

关于切面顺序研究

说明:spring中的AOP如果多个环绕通知参与,则采用嵌套的结构进行调用,执行的顺序与代码的顺序有关.

控制AOP的执行顺序用注解@Order(value=1):用value的值控制顺序

spring中默认代理策略

1.JDK动态代理 :被代理者必须有接口

2.CGLib代理:不管有没有接口都可以为其创建代理对象.代理对象是被代理者的子类(继承)

spring创建代理的规则:

如果目标对象有接口,则默认采用JDK动态代理模式

如果目标对象没有接口,则默认采用CGLib代理方式

代理模式的修改

说明如果需要改变默认的模式,则只能强制使用cglib代理模式(因为JDK动态代理的前提必须得有接口)

@EnableAspectJAutoProxy(proxyTargetClass = true)  //强制使用cglib动态代理
@EnableAspectJAutoProxy(proxyTargetClass = false)  //默认有接口使用JDK动态代理
@EnableAspectJAutoProxy  //默认有接口使用JDK动态代理
练习:

监控程序的运行时间 要求使用bean标签

记录目标对象类型class/目标对象的名称/目标方法的名称

1.定义接口和实现类

接口:

package com.jt.demo3.service;

public interface UserService {
    void addUser();
}

接口的实现类:

package com.jt.demo3.service;

import com.jt.demo3.anno.TX;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Override
    @TX
    public void addUser() {
        System.out.println("九九乘法表");
        for (int i = 1; i < 10; i++) {
            for (int j = 1; j <= i; j++) {
                System.out.print(i+"*"+j+"="+i*j);
            }
            System.out.println();
        }
    }
}
2.定义自定义注解
package com.jt.demo3.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TX {
}
3.定义配置类
package com.jt.demo3.config;

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

@Configuration
@ComponentScan("com.jt.demo3")
@EnableAspectJAutoProxy
public class Config {
}

4.定义切片类
package com.jt.demo3.aop;

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;

@Component
@Aspect
public class TxAspect {
    @Around("@annotation(com.jt.demo3.anno.TX)")  //直接把切入点表达式写在通知方法后面,只能用一次
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();

        //获取参数
        System.out.println("获取类型:"+joinPoint.getTarget().getClass());
        System.out.println("获取目标对象的名称:"+joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("获取方法名:"+joinPoint.getSignature().getName());

        System.out.println("耗时:"+(end-start));
        return result;
    }
}
5.测试类
package com.jt.demo3;

import com.jt.demo3.service.UserService;
import com.jt.demo3.config.Config;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestDemo {
    public static void main(String[] args) {
        ApplicationContext app = new AnnotationConfigApplicationContext(Config.class);
        UserService user = app.getBean(UserService.class);
        System.out.println(user.getClass());
        user.addUser();
    }
}
Spring总结 1.1 IOC-DI (1) IOC:控制反转

对象的生命周期由spring容器管理

xml配置文件的写法:bean标签 id/class xml的容器的实现类ClassPathXmlApplicationcontext

注解写法 @bean 配置类

单例多例问题 默认是单例对象 @scope(“prototype”)多例对象

懒加载 @lazy 用户使用时才创建

spring生命周期方法 对象创建(无参构造)/对象初始化/业务调用/对象销毁

DI:依赖注入:根据spring中维护的对象,为自己的对象属性赋值(为对象的属性赋值)

简单属性赋值: @value注解

引用赋值: @Autowired

注入的方式: 1.Set注入 2.构造方法注入 3.工厂模式注入

1.2 AOP

什么是AOP 面向切面编程 在不修改源码的条件下,对方法进行扩展!!!解决了业务逻辑的耦合性

底层核心机制:动态代理机制(JDK动态代理和CGLIB动态代理 )

关键词:

切入点表达式:表示如果用户调用满足切入点表达式,则创建动态代理对象,执行aop的通知

通知方法(对业务的扩展):5个通知,环绕通知最为强大

连接点:用户调用的方法满足切入点表达式,则该方法称作为连接点

1.3 Spring框架的优势

(1)Spring框架的设计让程序从层级到业务实现了松耦合

(2)Spring基于这样的设计思想,可以整合其它的第三方框架,使得程序可以以一种统一的方式进行

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/735741.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-27
下一篇 2022-04-27

发表评论

登录后才能评论

评论列表(0条)

保存