Day69.Spring: 使用注解开发、代理模式、AOP、切面、通知、AOP对IOC的影响、动态代理技术:JDK | cglib

目录

 

总结

使用注解开发

一、标记与扫描  ★

1.基本类型:HappyComponent的注解实现IoC (@Component  @Value)

2.引用类型:HappyMachine的注解实现IoC  (@Autowired)

二、分层开发自动装配  ★

注解管理IoC的总结

1. @Autowired的过程  ★

三、全注解开发   @ComponentScan  

四、Spring5整合Junit4  @RunWith @ContextConfiguration  ★

AOP (面向切面编程)  ★★★

一、代理模式  ★★

1. 静态代理

2. 动态代理 

二、AOP术语 (核心概念)  ★

三、AOP第一个示例:添加日志(准备)

四、AOP第一个示例:添加日志(实现)@Aspect  @Before.. 

五、重用切入点  @Pointcut

六、获取连接点 (信息)

七、切入点表达式语法  execution(* ... .method())

八、环绕通知  @Around (关于事物的切面)  ★

九、多个切面执行顺序 (切面优先级)  @Order

十、有无接口的动态代理技术 (JDK、CGLIB)  ★

十一、应用AOP 对 IoC中获取bean的影响 ★

十一、AOP 使用XML配置(了解)


总结

  • spring.xml 标签配置:
<!--指定注解扫描基准路径  会自动扫描base-package及其子包下的注解-->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 开启基于注解的AOP功能 -->
<aop:aspectj-autoproxy/>

<!-- 启动AOP自动代理
 proxy-target-class:底层采用什么动态代理技术
 false: 默认  有接口使用JDK动态代理,没有接口使用CGLIB
 true:不管是否有接口,都使用CGLIB-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
  • 创建Bean的注解:

@Component  标记该类,spring配置文件中base-package 扫描后会创建对象并装配到IoC容器

@Controller  标记控制器组件(控制层)

@Service  标记业务逻辑组件(业务逻辑层)

@Repository  标记持久化层组件(持久化、数据访问层)

@Configuration  说明这是一个配置类,要达到全注解开发还需要使用 @ComponentScan 

以上注解本质上相同,只是在@Component  基础上换了名字提高了代码的可读性。 

  • 注入Bean的注解:

@Value  基本类型的参数注入赋值,可以用在属性、set方法上

@Autowired  引用类型参数注入赋值,可以用在属性、set、构造方法上

@Qualifier  指定Bean的name;不会单独使用,而是和@Autowired配合使用

  • 全注解开发:

@ComponentScan()  组件扫描根据定义的扫描路径,把符合扫描规则的类装配到IoC容器中。

  • 整合Junit4:

@RunWith(SpringJUnit4ClassRunner.class)  指定Spring为Junit提供的运行器

@ContextConfiguration  指定Spring配置文件的位置

单个文件
@ContextConfiguration(locations = {"classpath:spring.xml"})
@ContextConfiguration("classpath:spring.xml") (locations{}也可省略)
@ContextConfiguration(classes = SimpleConfiguration.class)

多个文件
@ContextConfiguration(locations = {"classpath:spring.xml","classpath:spring2.xml"})
  • AOP 面向切面编程:声明切面类与通知方法

@Aspect  表示这个类是一个切面类

@Before(value)  前置通知方法

@AfterReturning(value)  返回通知方法

@AfterThrowing(value)  异常通知方法

@After(value)  后置通知方法

@Around(value):环绕通知方法 对应整个try...catch...finally结构,包括四种通知的所有功能
需要在对应方法声明 ProceedingJoinPoint 类型的形参,proceed() 调用切入点的方法。

value()属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上

@Aspect//这是一个切面类
public class LogAspect {
    //前置通知: 指定的方法在执行之前要做什么 (切入点,表达式)
    @Before("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))")
    public void beforeLog(){
        System.out.println("[日志] xxx 方法开始执行,参数是:");
    }
  • 创建IOC容器对象

BeanFactory  IoC容器的基本实现类
ApplicationContext  BeanFactory 的子接口,更多功能。

ClassPathXmlApplicationContext  根据XML配置文件创建IoC容器对象

// ClassPathXmlApplicationContext根据XML配置文件创建IOC容器对象
private ApplicationContext iocContainer = new ClassPathXmlApplicationContext("applicationContext.xml");

AnnotationConfigApplicationContext  根据XML配置类创建IoC容器对象 (全注解开发)

​// AnnotationConfigApplicationContext根据配置类创建IOC容器对象
private ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext(MyConfiguration.class);

WebApplicationContext  Web项目 

  • 切入点表达式语法

execution (* com.atguigu...method(int i))

  • 重用切入点

@Pointcut ("execution(切入点表达式)")

需要使用在一个方法上,使用时直接传入该方法即可

  • 获取连接点信息

获取方法信息:通知方法加入形参JoinPoint joinPoint方法内调用 getSignature()

获取方法返回值@AfterReturning中通过 returning属性设置名称,通知方法中声明对应名称Object类型 形参

获取方法抛出异常:@AfterThrowing中声明 throwing 属性设定名称,通知方法中声明对应名称声明 Exception类型 形参

环绕通知:声明 ProceedingJoinPoint 类型的形参,里面包含各种方法,可通过proceed() 调用切入点的方法。

  • 多个切面优先级

@Order(int i)  数值越小越早执行

使用注解开发

1、注解的作用

①注解

和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作

本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。

②扫描

Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

一、标记与扫描  ★

1.基本类型:HappyComponent的注解实现IoC (@Component  @Value)

  1. 使用@Component 标记该类,默认名称是该类名称首字母小写

  2. 使用@Value 基本类型的参数(包括String)赋值,位置可以在setter方法或者成员变量上。
    如果在setter方法上,就会使用反射调用setter方法。
    如果在成员变量上,就是使用反射直接操作Field成员变量。
    不能写在构造方法上

  3. 在spring配置文件中指定注解的扫描基准包base-package,会扫描该包及其子包下的注解,比如@Component,创建对象并放入IoC容器

//@Component //默认类名首字母小写
@Component(value = "happyComponent")
public class HappyComponent{
    @Value("engine")//给基本数据类型,包括String
    private String componentName;

    @Value("engine")
    public void setComponentName(String componentName) {
        this.componentName = componentName;
    }
    
    public void doWork() {
        System.out.println("doWork01--componentName:"+componentName);
    }
}
<!--指明注解扫描基准路径
    base-package:会扫描该包及其子包下的注解-->
<context:component-scan base-package="com.atguigu.pojo"></context:component-scan>

2.引用类型:HappyMachine的注解实现IoC  (@Autowired)

  1. 使用@Autowired 标记引用类型的成员变量(除了String),实现自动装配

  2. @Autowired可以写在成员变量、setter方法、构造方法上,均是通过反射实现注入

  3. 建议写在成员变量上

@Component
public class HappyMachine {
    @Value("BYD")
    private String machineName;//简单属性
    
    @Autowired  //自动装配
    private HappyComponent happyComponent;//复杂属性

    @Autowired
    public void setHappyComponent(HappyComponent happyComponent) {
        this.happyComponent = happyComponent;
    }

    @Autowired //构造器中不能有非引用参数
    public HappyMachine(HappyComponent happyComponent) {
        this.happyComponent = happyComponent;
    }

    public HappyMachine() {
    }
    public HappyMachine(String machineName, HappyComponent happyComponent) {
        this.machineName = machineName;
        this.happyComponent = happyComponent;
    }
}

二、分层开发自动装配  

f5d64ffa4b32450586543ffa9988e088.png

控制层
@Controller  //底层就是Component注解,为了可读性,应用于控制层
public class UserController {
    @Autowired    //自动装配
    private UserService userService;

    public void addUser(){
        userService.addUser();
    }
}
业务逻辑层
@Service(value = "userService")  //默认的名称:userServiceImpl
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public void addUser() {
        userDao.saveUser();
    }
}
持久化层
@Repository(value = "userDao") //底层就是Component注解,为了可读性,应用于Dao层
public class UserDaoImpl implements UserDao {
    @Override
    public void saveUser() {
        System.out.println("UserDaoImpl: saveUser");
    }
}
spring.xml
<!--指定注解扫描基准路径
        会自动扫描base-package及其子包下的注解-->
<context:component-scan base-package="com.atguigu"></context:component-scan>
测试
    @Test
    public void testIoC2_2(){
        //创建IoC的容器
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
        //从IoC获取指定Bean
        UserController userController = context.getBean("userController",UserController.class);
        userController.addUser();
    }

 异常:没有这样的对象定义。

NoSuchBeanDefinitionException: No bean named 'userController' available

114d248739f14cedaaed62109797d72b.png

原因:配置文件之扫描了pojo包,要改一下,(包下找不到userController对象)

注解管理IoC的总结

1. @Autowired的过程  

  • 按照ByType进行自动装配(如果只有一个,肯定可以装配成功)

  • 如果存在多个同类型的Bean,就会尝试ByName(变量名与类名)。如果有匹配的name,自动装配成功。

  • 如果没有匹配的name,就会尝试@Qualifier,匹配bean的name。

  • 如果@Qualifier也不匹配,报异常。

2. <context:component-scan base-package> 

  • base-package指定扫描的基准包

  • base-package 可以写多个,以逗号隔开

  • 扫描创建Bean的注解:扫描org.springframework.stereotype包下的四个注解

3. 创建Bean的注解 

  • @Component 通用性的注解,实际开发中可以替代@Controller @Service @Repository,但

  • @Controller 用在控制层

  • @Service 用在业务层

  • @Repository 用在DAO层

注解都写在实现类上,不能写在接口上。Bean的默认名称是类型首字母小写。

这四个注解本质是相同的都是@Component,只是为了代码可读性

4. 注入Bean的注解 

  • @Value:注入基本类型的参数(setter方法 属性)

  • @Autowired:注入引用类型的Bean(setter方法 构造方法 属性)

  • @Qualifier:指定Bean的name;不会单独使用,而是和@Autowired配合使用

5. @Autowired的位置 

  • setter方法:底层会使用反射调用setter方法 getMethod(“setUserDao”)

  • 构造方法:底层会使用反射调用构造方法 getContructor()

  • 成员变量:底层使用反射直接操作成员变量 getDeclaredField()

三、全注解开发   @ComponentScan  

体验完全注解开发,是为了学习SpringBoot打基础。因为在SpringBoot中,就是完全舍弃XML配置文件,全面使用注解来完成主要的配置。

使用注解开发,需要实现两个功能,目前XML的作用主要是两个:组件扫描、配置外部Bean

定义一个配置类

@Configuration //说明这是一个配置类
@ComponentScan(value = "com.atguigu")//组件扫描
public class SpringConfig {

    //一般用于第三方Bean操作。第三方的Bean无法使用注解
    @Bean //将当前方法中创建的Bean放入到IoC容器中,Bean的名称就是方法名
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis-example?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
        dataSource.setUsername("root");
        dataSource.setPassword("*******");
        return dataSource;
    }
}

测试类 

    //使用全注解开发
    @Test
    public void testAllAnnotation() throws SQLException {
        //1.创建IoC容器 (使用全注解,此时xml已经不存在了)
        //ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

        //2.congIoC容器中获取Bean
        DataSource dataSource = context.getBean("dataSource", DataSource.class);
        UserController userController = context.getBean("userController", UserController.class);

        //3.使用Bean
        System.out.println(dataSource);
        userController.addUser();
    }

四、Spring5整合Junit4  @RunWith @ContextConfiguration  

  • 好处1:不需要自己创建IOC容器对象了 (ClassPathXmlApplicationContext.getBean())
  • 好处2:任何需要的bean都可以在测试类中直接享受自动装配

1. 添加依赖

<!-- Spring的测试包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.1</version>
</dependency>

2.创建测试类

// junit的@RunWith注解:指定Spring为Junit提供的运行器
@RunWith(SpringJUnit4ClassRunner.class)
// Spring的@ContextConfiguration指定Spring配置文件的位置
@ContextConfiguration(locations = {"classpath:spring.xml","classpath:spring2.xml"})
public class TestIoC3 {
    @Autowired
    private DataSource dataSource;
    @Autowired
    private UserController userController;
    
    @Test
    public void testIntegration() throws SQLException {
        //减少了创建IoC容器、装配的操作,直接使用
        System.out.println(dataSource.getConnection());
        userController.addUser();
    }
}

AOP (面向切面编程)  ★★★

AOP (Aspect Oriented Programming) 面向切面编程

通过预编译方式和运行期间动态代理在不修改源代码的情况下给程序动态统一的添加功能的一种技术,简称AOP。是spring框架的一个重要内容,可以说是对OOP (面向对象编程) 的补充和完善

OOP解决纵向的业务问题。

AOP解决横向的问题,比如日志、安全验证、事务、异常

a2f07d97a4e6417aa82c4af614119933.png

一、代理模式  ★★

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法时,不再是直接对目标方法进行调用,而是通过代理类间接调用解耦

代理类符合了开闭原则:对修改关闭对扩展开放

  • 目标类(TargetSubject):具体实现业务功能的类 (核心逻辑代码);

  • 代理类(ProxyObject):提供一些被代理类功能(核心逻辑代码)之外的额外功能的类

  • 请求类(RequestObject):业务调用类。

Day34.反射-动态代理、动态代理与AOP_焰火青年·的博客-CSDN博客

1. 静态代理

优点:

  1. 不需要去修改目标类代码,就可以增加新的功能(比如日志)。目标类只关注业务即可

  2. 代理所有的实现了该接口的目标类。

缺点:

  1. 在编译时就已经将接口、被代理类、代理类等确定下来,只能代理一个接口的目标类。代码都写死了,不具备任何的灵活性,共同方法没有进行统一管理

  2. 静态代理类是需要开发者亲自写代码开发出来的,看得见摸得着的。

定义静态代理类,要求代理类实现和目标类实现一样的接口

/*
* 静态代理: 实现了角色分离
*   关键点:
*   关联和目标类一样的接口,可以来指向真正的目标类,从而调用目标类方法
* */
public class ShoesFactoryStaticProxy implements ShoesFactory {
    //相同的接口
    ShoesFactory shoesFactory;
    //构造器,set方法,指定目标类
    public ShoesFactoryStaticProxy(ShoesFactory shoesFactory) {
        this.shoesFactory = shoesFactory;
    }
    public void setShoesFactory(ShoesFactory shoesFactory) {
        this.shoesFactory = shoesFactory;
    }
    //调用方法
    @Override
    public Shoes production() {
        beforeLog();    //日志方法
        Shoes shoes = shoesFactory.production();
        afterLog();
        return shoes;
    }
    //日志方法
    public void beforeLog(){
        System.out.println("[日志] 生产开始了!");
    }
    public void afterLog(){
        System.out.println("[日志] 生产结束了!");
    }
}

测试静态代理类

public class Test {
    public static void main(String[] args) {
        //创建一个经纪人
        CalculatorLogStaticProxy proxy = new CalculatorLogStaticProxy();

        //指定代理类代理谁
        Calculator calculator = new CalculatorPureImpl();
        proxy.setCalculator(calculator);

        //调用代理类的方法
        int result = proxy.add(10, 20);
        System.out.println(result);
    }
}

目标类 

//鞋子
public class Shoes {
    public String name;
}
//鞋子制造厂
public interface ShoesFactory {
    public Shoes production();
}
//阿迪达斯制造厂
public class AdidasShoesFactoryImpl implements ShoesFactory {
    @Override
    public Shoes production() {
        Shoes shoes = new Shoes("阿迪达斯");
        return shoes;
    }
}
//耐克制造厂
public class NikeShoesFactoryImpl implements ShoesFactory {
    @Override
    public Shoes production() {
        Shoes shoes = new Shoes("耐克");
        return shoes;
    }
}

2. 动态代理 

代理类在程序运行时创建的代理方式被成为动态代理

相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法 (共同方法解耦)。 

AOP底层原理就是动态代理技术

3ddbdae969dd4762a094a50fa78bc283.png

定义动态代理工厂类

/*
* 日志代理类的工厂
* 代理日志功能,都是接口可以代理
* 工厂: 由这个类来产生代理对象并返回
* */
public class LogDynamicProxyFactory<T> {//泛型接口,可以传入目标接口
    private T target; //指向目标类
    public void setTarget(T target){
        this.target = target;
    }
    //创建代理对象并返回
    public T getProxy(){
        //目标类的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //目标类实现的接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        /*
        * 代理对象要干什么?
        * Object proxy: 代理对象
        * Method method: 目标类的方法 比如add(int i,int j) saveUser()
        * Object[] args: 目标类方法的参数: (int i,int j)
        * */
        InvocationHandler invocationHandler = (Object proxy, Method method, Object[] args) -> {
            Object result = null;
            try{
                //新皇登基: 前置通知
                System.out.println("[日志]" +method.getName()+ " 方法开始执行了,方法的参数是:"+ Arrays.toString(args));

                result = method.invoke(target, args);//让大明星拍广告

                //寿终正寝: 返回通知
                System.out.println("[日志]" +method.getName()+ " 方法执行结束了,结果是:"+result);
            }catch (Exception e){
                //死于非命: 异常通知
                System.out.println("[日志]" +method.getName()+ "方法出现了异常,异常信息:"+e.toString());
            }finally {
                //风光大葬: 后置通知
                System.out.println("[日志] finally 收尾工作");
            }
            return result;
        };
        return (T) Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
    }
}

测试类

public class TestDynamicProxy {
    @Test
    public void testCalculator(){
        //创建动态代理工厂类的对象
        LogDynamicProxyFactory<Calculator> factory = new LogDynamicProxyFactory();
        factory.setTarget(new CalculatorPureImpl());
        //由工厂创建动态代理对象
        Calculator proxy = factory.getProxy();

        int add = proxy.add(10, 20);
        System.out.println(add);
    }
    @Test
    public void testUser(){
        //创建动态代理工厂类的对象
        LogDynamicProxyFactory<UserDao> factory = new LogDynamicProxyFactory();
        factory.setTarget(new UserDaoImpl());
        //由工厂创建动态代理对象
        UserDao proxy = factory.getProxy();
        proxy.saveUser();
    }
}

二、AOP术语 (核心概念)  

概念描述

Spring中

横切关注点对哪些方法进行拦截,拦截后怎么处理
通知|增强 advice拦截到连接点之后要做的事情。@Before 表现为一个方法。分为前置、后置、异常、最终、环绕五类
连接点 joinpoint在哪些地方加入通知?被拦截到的目标类的方法。add() sub)
切入点 pointcut定位连接点的方式。一个表达式,能涵盖所有的连接点
切面 aspect类是对物体特征的抽象,切面就是对横切关注点的抽象。@Aspect 修饰的类切面是一个类,包括两部分:通知(做什么)+ 切入点(在哪做)
目标 target

被代理的目标对象。

被代理的目标对象。(如 NikeFactory)
代理 proxy向目标对象应用通知之后创建的代理对象。(...DynamicProxy)
织入 weaving把切面应用到目标上,生成代理对象的过程。

可以在编译期织入,也可以在运行期织入,Spring采用后者。

1、横切关注点 

  • 纵向关注点核心关注点 业务关注点):add()  sub()  findAll()  insert()  OOP解决纵向关注点

  • 横切关注点非业务的功能) 日志、安全验证、事务、异常。AOP解决横切关注点

dec368f9f6884d128a62cef30d963729.png

2、通知|增强 (advice) 

每一个横切关注点上要做的事情称为通知。通知需要写一个方法来实现。也被称为增强。

就是AOP的要做的事情,比如写日志,比如要进行事务处理。在Spring中,通知表现为一个方法。

  • 前置通知:在被代理的目标方法前执行(新皇登基)@Before

  • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)@AfterReturning

  • 异常通知:在被代理的目标方法异常结束后执行(死于非命)@AfterThrowing

  • 后置通知:在被代理的目标方法最终结束后执行(风光大葬)@After

  • 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置和功能

3、连接点 (joinpoint) 

 在什么地方加入通知呢? 在add()  sub()  findAll()  在Spring中,连接点是一个方法

这也是一个纯逻辑概念,不是语法定义的。指那些被拦截到的点。在 Spring 中,可以被动态代理拦截目标类的方法。

4、切入点 (pointcut) 

一个表达式,能涵盖所有的连接点的表达式

execution(* com.atguigu.service.CalculatorImpl.*(..)) 作用与所有方法

execution(* com.atguigu.service.CalculatorImpl.add(..))  只作用域add方法

过滤器:<url-pattern>*.html</url-pattern>   切入点 (过滤以html结尾的)

连接点:http://www.atguigui.com/adfa/adfa/index.html 连接点 (过滤具体的请求路径)

<filter-mapping>
   <filter-name>AdminFilter</filter-name>
   <url-pattern>*.html</url-pattern>
</filter-mapping>

5、切面 (aspect) 

切入点通知的结合。是一个类,通知(干什么)+切入点(在哪里干)

类比过滤器:定义一个类(EncodingFilter)+过滤路径(<url-pattern>*.html</url-pattern>)

7f6a46c9e7b04c93b1f8ba6ae527b29b.png

6、目标( target) 

被代理的目标对象。(UserDaoImpl  CalculatorPureImpl )

7、代理 (proxy)  (静态代理和动态代理)

向目标对象应用通知之后创建的代理对象

8、织入 (weave) (目标 + 通知 --> 代理对象) 

指把通知应用到目标上,生成代理对象的过程。可以在编译期织入,也可以在运行期织入,Spring采用后者。

9、切面和过滤器有什么共同点和不同点 

共同点:都可以用来解决横切性问题,比如日志、事务

不同点:

Filter必须是Web项目,离不开Servlet容器。 AOP没有这个要求

Filter过滤是http请求的路径AOP过滤的是方法(哪些包下哪些类的哪些方法)

三、AOP第一个示例:添加日志(准备)

总体思路:使用OOP实现计算器的业务功能,使用AOP在不修改计算器代码的基础上增强日志功能。fcf48cbb24e840e8ba6813a974d7a84f.png

1cd389164c23436f86bf44a2cfc047a7.png

1. 添加依赖

<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<!-- Spring的测试包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.1</version>
</dependency>

2. 定义目标接口和目标类

//计算器接口
public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
//实现类
@Component
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}

3. 配置(扫描注解)

    <!--指明注解扫描基准路径  base-package:会扫描该包及其子包下的注解-->
    <context:component-scan base-package="com.atguigu"></context:component-scan>

4. 测试

// junit的@RunWith注解:指定Spring为Junit提供的运行器
@RunWith(SpringJUnit4ClassRunner.class)
// Spring的@ContextConfiguration指定Spring配置文件的位置
@ContextConfiguration(locations = "classpath:spring.xml")
public class TestAOP {
    @Autowired//引用类型自动匹配
    Calculator  calculator;
    @Test
    public void test(){
        int result = this.calculator.add(10, 20);
        System.out.println(result);
        System.out.println("---------------------------");
        result = this.calculator.div(10, 0);
        System.out.println(result);
    }
}

四、AOP第一个示例:添加日志(实现)@Aspect  @Before.. 

1. 添加依赖

<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>

2. 定义切面(通知+切入点)

@Component //组件扫描,被<context:component-scan>扫描到,创建对象并放入IoC容器
@Aspect //这是一个切面
public class LogAspect {
    //前置通知: 指定的方法在执行之前要做什么 (切入点,表达式)
    @Before("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))")
    public void beforeLog(){
        System.out.println("[日志] xxx 方法开始执行,参数是:");
    }
    //返回通知
    @AfterReturning("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))")
    public void afterReturningLog(){
        System.out.println("[日志]" +" 方法执行结束了,结果是:");
    }
    //异常通知
    @AfterThrowing("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))")
    public void afterThrowingLog(){
        System.out.println("[日志]" +"方法出现了异常,异常信息:");
    }
    //后置通知
    @After("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))")
    public void afterLog(){
        System.out.println("[日志] finally 收尾工作");
    }
}

3. 配置文件中启动AOP

<!-- 注解扫描:@Component @Service @Controller @Reposity-->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 启动AOP自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

4. 测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class TestAOP {
    @Autowired
    Calculator calculator;
    @Test
    public void testAdd(){
        int add = calculator.add(10, 20);
        System.out.println(add);
        System.out.println("-------");
        int div = calculator.div(10, 0);
        System.out.println(div);
        System.out.println(div);
    }
}

 32fad5fe4a5c47a19f703a40189b6f3b.png

五、重用切入点  @Pointcut

在一处声明切入点表达式之后,其他有需要的地方引用这个切入点表达式。易于维护,一处修改,处处生效。

可以创建存放切入点表达式的类,可以把整个项目中所有切入点表达式全部集中过来,便于管理。

    //重用切入点
    @Pointcut("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))")
    public void pointcut1(){

    }

同一个类中使用:

    //前置通知: 指定的方法在执行之前要做什么 (切入点,表达式)
    @Before("pointcut1()")
    public void beforeLog(){
    ...
    }

不同类中使用:

@Around(value = "com.atguigu.aspect.LogAspect.pointcut1()")
public Object roundAdvice(ProceedingJoinPoint joinPoint) {
}

六、获取连接点 (信息)

1、方法信息 (JoinPoint接口)

org.aspectj.lang.JoinPoint

  • 要点1:JoinPoint接口通过 getSignature() 方法获取目标方法的签名
  • 要点2:通过目标方法签名对象获取方法名
  • 要点3:通过JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数
    @Before("pointcut1()")
    public void beforeLog(JoinPoint joinPoint){  //连接点
        Object[] args = joinPoint.getArgs();//获取方法参数
        Signature signature = joinPoint.getSignature();//获取签名
        String name = signature.getName();//获取方法名

        System.out.println("[日志] "+name+" 方法开始执行,参数是:"+Arrays.toString(args));
    }

2、方法返回值

1.在 @AfterReturning通过 returning 属性设置一个名称

2.使用returning 属性设置的名称在通知方法中声明一个对应的形参

c66b306d2575491a8d060cce2f64ffca.png

    //返回通知
    @AfterReturning(value = "pointcut1()",returning = "result")
    public void afterReturningLog(Object result){   //返回值 注意类型是Object
        System.out.println("[日志]" +" 方法执行结束了,结果是:" +result);
    }

3、目标方法抛出的异常

@AfterThrowing 中声明 throwing 属性设定形参名称

使用 throwing 属性指定的名称在通知方法声明形参

59b291b256ba4788a73a3801f38f839d.png

    @AfterThrowing(value = "pointcut1()",throwing = "exception")
    public void afterThrowingLog(JoinPoint joinPoint,Exception exception){
        Signature signature = joinPoint.getSignature();//获取修饰符
        String name = signature.getName();
        System.out.println("[日志]"+ name +"方法出现了异常,异常信息:"+exception.toString());
    }

七、切入点表达式语法  execution(* ... .method())

切入点最终会落到方法上面

c188e96c031540199117a62d5d4079a5.png

1. 某包某类的无参数的方法::execution(* com.atguigu.service.impl.Student.test())

2. 某包某类带有参数的方法: execution(* com.atguigu.service.impl.Student.test(String,int))

3. 某包某类的某个同名的所有方法:execution(* com.atguigu.service.impl.Student.test(..))
.. 表示任意个数任意类型的参数

4. 某类的所有方法:execution(* com.atguigu.service.impl.Student. * (..))
* 表示任意的类名方法名包名

5. 所有类的所有方法:execution(* com.atguigu.service.impl.* . *(..))

  • * *.Student.test(..)  包只有一级,包名任意,com.Student,cn.Student
  • * *..Student.test(..)   所有层级的包都匹配,可以是com.Student,com.atguigu.Student
  • * com.atguigu.service.impl.*Service.test() 指定包下类名须是Service结尾
  • execution(public int *..*Service.*(.., int)) 可以
  • execution(* int *..*Service.*(.., int)) 修饰符任意 不可以
  • execution(public * *..*Service.*(.., int)) 返回值任意 可以

八、环绕通知  @Around (关于事物的切面)  ★

环绕通知对应整个try...catch...finally结构包括四种通知的所有功能,属于一个通知。

注意环绕通知需要返回 指定连接点方法的 返回值

//环绕通知: 关于事物的切面
@Aspect    //声明为一个切面类
@Component
public class TransactionAspect {
    //CalculatorImpl.*(..)) 链接点为该类下的所有方法
    @Around("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))")
    public Object doTransaction (ProceedingJoinPoint pjp){
        //调用目标方法
        Object proceed = null;
        try{
            //设置事物不在自动所提交
            System.out.println("[事务] 设置事物不再自动提交 conn.setAutoCommit(false)");
            //调用方法
            proceed = pjp.proceed();
            //提交事务
            System.out.println("[事务] 成功结束,手动提交 conn.commit()");
        }catch (Throwable e){ //Throwable Exception父类
            e.printStackTrace();
            //手动回滚事务
            System.out.println("[事务] 异常结束,手动回滚 conn.rollback()");
        }finally {
            //关闭资源
            System.out.println("[事务] 无论成功失败,关闭链接 conn.close()");
        }
        //返回调用方法的返回值
        return proceed;
    }
}

321fb9aac423487dabb396c98588e3a4.png

九、多个切面执行顺序 (切面优先级)  @Order

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序

通过加入@Order(int)   参数越小越早执行。

@Aspect //声明为切面类
@Component
@Order(2)//越小越早执行
public class TransactionAspect {
@Component //组件扫描,被<context:component-scan>扫描到,创建对象并放入IoC容器
@Aspect //这是一个切面
@Order(3)//越小越早执行
public class LogAspect {

f753d43ec88a4c86999c8a9d75b8ad1c.png

64754592e6994dee9b2c9aa78ef477c1.png

十、有无接口的动态代理技术 (JDK、CGLIB)  ★

两种动态代理技术:

JDKSUN公司官方提供的,Proxy为JDK动态代理类的父类

CGLIB第三方提供的动态代理的技术,更加强大

  • 情况1:没有切面:创建的当前类对象自身,直接从IoC容器获取,没有使用动态代理。

3272351e6e5b435d953ea217d7393de4.png

  • 情况2:
    使用切面,但是bean对应的类有接口(CalculatorPureImpl底层自动使用 JDK 的动态代理)
    使用切面,但是bean对应的类没有接口(HappyComponent 自动的使用 CGLIB 的动态代理)

fcdf3ccc24ec4112b45e85b261cce9d4.png

  • 情况3:使用切面,不管是否有接口,都使用CGLIB (通过配置AOP自动代理)
<!-- 启动AOP自动代理
 proxy-target-class:底层采用什么动态代理技术
 false : 默认  有接口使用JDK动态代理,没有接口使用CGLIB
 true:不管是否有接口,都使用CGLIB
-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

4acb76f476974d8ea187930866772407.png

问题1:为什么使用JDK动态代理,目标Bean对象必须有接口

因为JDK动态代理生成的代理类已继承了Proxy,而Java是单继承的,所以必须有接口。

6a848bafa46c45649614594e10705094.png

问题2:为什么使用CGLIB实现动态代理,目标Bean对象不要求有接口

 因为CGLIB生成的代理类没有固定的父类,所以可以直接继承目标类不需要接口

ce7020eae59d47db9861acc1b148745b.png

十一、应用AOP 对 IoC中获取bean的影响 ★

1、情况一:没有使用AOP切面

根据bean本身、或接口的类型,正常获取到IOC容器中的那个bean对象

注意:使用接口IoC容器中只能有一个实现类;使用实现类IoC容器中有一个实例

否则抛出异常:NoUniqueBeanDefinitionException,表示IOC容器中这个类型的bean有多个。

1786add6fb3c4885830c9c9f7359c0d5.png

2、情况2: 使用AOP,但是使用JDK动态代理(Proxy)

<aop:aspectj-autoproxy proxy-target-class="false"/>

使用接口可以获取,使用实现类不可以。原因,JDK动态代理继承了Proxy,而java是单继承

b6cbce410a8f422d9eaa47df9ec27795.png

应用了切面后,真正放在IOC容器中的是代理类的对象,目标类并没有被放到IOC容器中,所以根据目标类的类型从IOC容器中是找不到的。

从内存分析的角度来说,IOC容器中引用的是代理对象,代理对象引用的是目标对象。IOC容器并没有直接引用目标对象,所以根据目标类本身在IOC容器范围内查找不到。 

a236180ff14e406a874e024636ddda80.png

3、情况3: 使用AOP,但是使用CGLIB

<aop:aspectj-autoproxy proxy-target-class="true"/>

使用接口、实现类都可以获取。

983a5ba55674431d8d9d2a4c5faa07fe.png

 ddac65aeb0b447f589d0d506bbf95b07.png

十一、AOP 使用XML配置(了解)

目前Spring的开发以注解开发为主。XML配置基本不使用。

spring.xml
<!--配置目标类 -->
    <bean id="calculator" class="com.atguigu.service.impl.CalculatorPureImpl"></bean>

    <!-- 配置切面类-->
    <bean id="logAspect" class="com.atguigu.aspect.LogAspect"></bean>

    <!-- 配置切面-->
    <aop:config>
        <!--配置日志切面 -->
        <aop:aspect ref="logAspect" order="3">
            <!-- 配置切入点-->
            <aop:pointcut id="pointcut1" expression="execution(public * com.atguigu.service.impl.*.*(..))"/>
            <!--配置各种通知-->
            <aop:before method="beforeLog" pointcut-ref="pointcut1"></aop:before>
            <aop:after-returning method="afterReturningLog"
                                 pointcut-ref="pointcut1"
                                 returning="resultValue"></aop:after-returning>
            <aop:after-throwing method="afterThrowingLog"
                                pointcut-ref="pointcut1"
                                throwing="exception"></aop:after-throwing>
            <aop:after method="afterLog" pointcut-ref="pointcut1"></aop:after>
        </aop:aspect>
        <!--配置事务切面...待补充 -->
    </aop:config>
对应使用注解方式
@Component //组件扫描,被<context:component-scan>扫描到,创建对象并放入IoC容器
@Aspect //这是一个切面
@Order(3)//越小越早执行
public class LogAspect {
    //重用切入点
    @Pointcut("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))")
    public void pointcut1(){

    }
    //前置通知: 指定的方法在执行之前要做什么 (切入点,表达式)
    @Before("pointcut1()")
    public void beforeLog(JoinPoint joinPoint){  //连接点
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();//获取修饰符
        String name = signature.getName();

        System.out.println("[日志] "+name+" 方法开始执行,参数是:"+Arrays.toString(args));
    }

    //返回通知
    @AfterReturning(value = "pointcut1()",returning = "result")
    public void afterReturningLog(Object result){   //返回值 注意类型是Object
        System.out.println("[日志]" +" 方法执行结束了,结果是:" +result);
    }
    //异常通知
    @AfterThrowing(value = "pointcut1()",throwing = "exception")
    public void afterThrowingLog(JoinPoint joinPoint,Exception exception){
        Signature signature = joinPoint.getSignature();//获取修饰符
        String name = signature.getName();
        System.out.println("[日志]"+ name +"方法出现了异常,异常信息:"+exception.toString());
    }
    //后置通知
    @After("pointcut1()")
    public void afterLog(){
        System.out.println("[日志] finally 收尾工作");
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring AOP中的代理模式是指通过生成一个代理类,替换真实的类去对外提供服务。在Spring IOC容器中,通过使用代理类来控制对真实对象的访问。代理模式Spring AOP中主要包括静态代理JDK动态代理CGLib动态代理三种方式。 静态代理是指代理类在编译期就存在的方式,代理类在之前类的基础上进行了一层封装。在静态代理中,代理类持有一个被代理类的实例,在代理类中调用被代理对象的方法,并可以在方法之前或之后加入其他的方法处理逻辑。 JDK动态代理是在程序运行期动态生成代理类的方式。通过Java提供的Proxy和InvocationHandler接口,可以动态地生成代理类,实现对目标对象的代理CGLib动态代理也是在程序运行期动态生成代理类的方式。与JDK动态代理不同的是,CGLib动态代理通过继承的方式生成代理类,而不是实现接口。 总结来说,Spring AOP中的代理模式是通过生成代理类来控制对真实对象的访问。可以使用静态代理JDK动态代理CGLib动态代理的方式来实现代理模式代理类可以在调用被代理对象的方法之前或之后加入其他的方法处理逻辑。这样可以实现一些公共的行为,如日志记录、权限验证等,避免在每个业务方法中重复编写相同的代码,提高代码的复用性和可维护性。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值