SSM整合尚硅谷Spring

Spring(一)----IOC控制反转、DI依赖注入

Spring基础知识学习笔记(一),内容包括:

  1. Spring入门案例

  2. IOC控制反转理解

  3. 属性注入的不同方式

  4. 注入不同类型的属性值

  5. 自动装配与注解开发

参考视频:

B站 尚硅谷雷丰阳大神的Spring、Spring MVC、MyBatis课程

【狂神说Java】Spring5最新完整教程IDEA版通俗易懂

1. Spring概述

  • 开源的免费框架,是一个容器,可以管理所有的组件(类);

  • 轻量级的、非入侵的框架,不依赖于Spring的API

  • 控制反转(IOC)和面向切面编程(AOP)

  • 支持事务处理,支持对框架整合

  • 组件化、一站式

官网: https://spring.io

文档:https://docs.spring.io/spring/docs/5.2.5.RELEASE/spring-framework-reference/core.html#spring-core

【总结】:Spring是一个轻量级的、控制反转和面向切面编程的框架

体系结构: 在这里插入图片描述

  • Test:Spring的单元测试模块

  • Core Container:核心容器(IOC),包括4部分:

    • spring-core:提供了框架的基本组成部分,包括 IoC 和依赖注入功能。

    • spring-beans:提供 BeanFactory,

    • spring-context:模块建立在由core和 beans 模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。Context模块继承自Bean模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过Servelet容器)等功能

    • spring-expression:提供了强大的表达式语言,用于在运行时查询和操作对象图。它是JSP2.1规范中定义的统一表达式语言的扩展,支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等

  • AOP+Aspects:面向切面编程模块

  • Data Access:数据访问模块

  • Web:Spring开发Web引用模块

导入依赖:spring-webmvc 包含的最广泛

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
123456

2. HelloWorld案例

2.1 IOC和DI

Inversion of Control:控制反转。

主动式:自己需要什么自己创建

 

BookServlet{
    BookService bs = new BookService(); 
    AirPlane ap =new AirPlane();
}

 

被动式:

BookServlet{
    BookService bs ;
    public void test(){
        bs.checkout();
    }
}

 

控制,即资源的获取方式,包括:

  • 主动式:要什么资源自己创建,对于复杂对象的创建时比较庞大的工程

  • 被动式:资源的获取不是我们自己创建,而是交给容器创建。

    所谓容器,是用来管理所有的组件的(即有功能的类);BookServlet,BookService都受容器管理,容器可以自动探查出哪些组件需要用到另一些组件;容器帮我们创建BookService 对象,并且把BookService 对象赋值过去;

容器:婚介所:

主动获取变为被动接受

程序员只需要告诉容器在什么时候创建什么对象

DI:Dependency Injection,依赖注入,是IOC的一种实现形式。容器能知道哪个组件(类 )运行时需要另外一个类,容器通过反射的形式,将容器中准备好的BookService对象注入(用反射)到BookServlet中

只要是容器管理的组件,都能使用容器提供的强大功能

反射:

Class stuClass1 = stu1.getClass();
Class stuClass2 = Student.class;
Class stuClass3 = Class.forName("fanshe.Student");

 

https://blog.csdn.net/sinat_38259539/article/details/71799078?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160548849819725225033252%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=160548849819725225033252&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-2-71799078.pc_first_rank_v2_rank_v28&utm_term=%E5%8F%8D%E5%B0%84&spm=1018.2118.3001.4449

2.2 入门案例

HelloWorld:所有的对象交给容器创建,给容器中注册组件

  1. 新建一个Person类,添加set方法

    public class Person {
    ​
        private String lastName;
        private Integer age;
        private String gender;
        private  String email;
    ​
        public Person() {
        }
    ​
        public Person(String lastName, Integer age, String gender, String email) {
            this.lastName = lastName;
            this.age = age;
            this.gender = gender;
            this.email = email;
        }
    ​
        public String getLastName() {
            return lastName;
        }
    ​
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    ​
        public Integer getAge() {
            return age;
        }
    ​
        public void setAge(Integer age) {
            this.age = age;
        }
    ​
        public String getGender() {
            return gender;
        }
    ​
        public void setGender(String gender) {
            this.gender = gender;
        }
    ​
        public String getEmail() {
            return email;
        }
    ​
        public void setEmail(String email) {
            this.email = email;
        }
    ​
        @Override
        public String toString() {
            return "Person{" +
                    "lastName='" + lastName + '\'' +
                    ", age=" + age +
                    ", gender='" + gender + '\'' +
                    ", email='" + email + '\'' +
                    '}';
        }
    }
    ​
  2. 新建一个Spring配置文件ApplicationContext.xml,注册bean。

    使用bean标签注册一个Person对象,Spring会自动创建这个Person对象

    • class:写要注册的组件的全类名

    • id:这个对象的唯一标识

    • 使用property标签为Person对象的属性值,name:指定属性名;value:指定属性值

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    ​
        <!--注册一个Person对象,Spring会自动创建这个Person对象
            class:写要注册的组件的全类名,地址
            id:这个对象的唯一标识
        -->
        <bean id="person01" class="com.xiao.bean.Person">
            <!--使用property标签为Person对象的属性赋值
                name:指定属性名
                value:指定属性值
             -->
            <property name="lastName" value="zhangsan"/>
            <property name="age" value="20"/>
            <property name="email" value="zhangsan@163.com"/>
            <property name="gender" value="0"/>
        </bean>
    </beans>
    ​
    1. 测试:

       public class IocTest {
       
           @Test
           public void test(){
          //从容器中拿到这个组件
               /*
               ApplicationContext:代表IOC容器
               ClassPathXmlApplicationContext当前应用的xml配置文件在classpath下的ApplicationContext.xml
               根据spring的配置文件得到ioc容器对象
               对象创建是容器做的
               */
              ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml");
              Person bean = (Person) ioc.getBean("person01");
               System.out.println(bean);
               
           }
       }

     

    【几个细节】:

    1. ApplicationContext:IOC容器的接口

    2. ClassPathXmlApplicationContext("ioc.xml"):ioc容器的配置文件在类路径下,

      FileSystemXmlApplicationContext("d://ioc.xml")ioc容器的配置文件在磁盘路径下

    3. 同一个组件在IOC容器中默认是单实例的

    4. 容器中的对象的创建在容器创建完成的时候就已经创建好了

    5. 容器中如果没有这个组件,获取组件时会报异常 NoSuchBeanDefinitionException

    6. IOC容器用property标签创建这个组件对象的时候,会利用setter方法为其属性赋值,注意属性名是set方法后的那串的首字母小写

2.3 根据bean类型获取bean实例

ioc.getBean()方法中可以传入bean的id,也可以传入class对象,也可以同时传入。

如果一个类型指只注册了一个,则可以通过ioc.getBean(....class)获得该对象

Person bean1 = ioc.getBean(Person.class);
//找到Person类型
sout(bean);

但是如果IOC容器中这个类型的bean有多个,则会报异常 NoUniqueBeanDefinitionException

也可以同时传入bean的id和class对象:

Person bean1 = ioc.getBean("person02",Person.class);

3. 属性的注入方式

  • 依赖:bean对象的创建依赖于容器

  • 注入:bean对象中所有的属性由容器来注入

3.1 setter注入

需要借助set方法,使用propetry标签

 <property name="lastName" value="zhangsan"/>
1

3.2 通过构造器注入

使用constructor-arg构造器参数标签,则调用构造器进行属性注入,需要借助有参构造

  • 通过构造函数中的参数名称注入

  <bean id="person" class="com.xiao.bean.Person">
        <constructor-arg name="lastName" value="wangwu"/>
        <constructor-arg name="age" value="30"/>
  </bean>
​
  • 只写value属性,会默认按顺序寻找构造方法进行匹配

  <bean id="person" class="com.xiao.bean.Person">
        <constructor-arg  value="wangwu"/>
        <constructor-arg  value="30"/>
  </bean>
1234
  • 通过构造函数参数类型,默认按照顺序

  <bean id="person" class="com.xiao.bean.Person">
      <constructor-arg type="java.lang.String" value="wangwu"/>
      <constructor-arg type="java.lang.Integer" value="30"/>
  </bean>
1234
  • 通过构造函数参数索引,如果有多个重载的构造函数时也可以配合type一起使用

  <bean id="person" class="com.xiao.bean.Person">
      <constructor-arg index="0" value="wangwu"/>
      <constructor-arg index="1" value="30"/>
  </bean>
1234

3.3 p名称空间注入

使用p:propertyName直接注入属性的值。本质上还是调用的set方法

导入头文件约束:

 增加 xmlns:p="http://www.springframework.org/schema/p"
​
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean id="person01" class="com.xiao.bean.Person">
        <property name="lastName" value="zhangsan"/>
        <property name="age" value="20"/>
        <property name="email" value="zhangsan@163.com"/>
        <property name="gender" value="0"/>
    </bean>
​
    <bean id="person04" class="com.xiao.bean.Person"
        p:lastName="zhangsan" p:age="30" p:email="zhangsan@qq.com" p:gender="1">
    </bean>
​
</beans>

3.4 c命名空间注入

c(构造: Constructor)命名空间注入,使用c:propertyName注入属性值,本质上使用的是构造器注入

导入头文件约束:

 xmlns:c="http://www.springframework.org/schema/c"
1
  <bean id="person05" class="com.xiao.bean.Person"
          c:lastName="zhangsan" c:age="30" c:email="zhangsan@qq.com" c:gender="1">
    </bean>
123

4. 注入不同类型的属性值

新建了一个Student类和一个Address类,来测试不同类型的属性值注入:

Student类:

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map card;
    private Set<String> games;
    private String wife;
    private Properties info;
    //get/set方法
    //...  
}
123456789101112

Address类:

public class Address {
    private String name;
    private Integer num;
    //get/set方法
    //...  
}
123456

4.1 注入基本类型值

之前的例子都是注入基本类型的属性。如果不赋值的话,会使用属性的默认值

4.2 注入null

如果有属性给了初始值,想注入为null,则在property内部需要使用null标签:

    <bean id="student01" class="com.xiao.bean.Student">
        <property name="name">
            <null/>
        </property>
    </bean>
​

注意,使用value="null"是不对的:

 <bean id="student01" class="com.xiao.bean.Student">
        <property name="name" value="null"/>
  </bean>

上面的用法虽然对象的name属性打印出来是null,但是bean.getName()==null是false:

Student bean = ioc.getBean("student01", Student.class);
System.out.println(bean);
System.out.println(bean.getName()==null);  //false
123

结果:

Student{name='null', address=null, books=null, hobbys=null, card=null, games=null, false
1

4.3 注入bean

可以使用ref引用外部的值:

<!--先注册一个Address对象-->
<bean id="address01" class="com.xiao.bean.Address">
    <property name="name" value="beijing"/>
    <property name="num" value="001"/>
</bean>
​
<bean id="student02" class="com.xiao.bean.Student">
    <!--通过id值引用-->
    <property name="address" ref="address01"/>
</bean>

要注意,ref是严格的引用,通过容器拿到的Address实例就是Student实例中的Address属性

Address address01 = ioc.getBean("address01", Address.class);
Student student02 = ioc.getBean("student02", Student.class);
System.out.println(student02);
System.out.println(student02.getAddress() == address01);  //true

也可以引用内部bean,在property标签体中再定义bean,这个Address和外面的没有关系,只能内部使用,外面获取不到

<bean id="student03" class="com.xiao.bean.Student">
    <property name="address">
        <bean class="com.xiao.bean.Address">
            <property name="name" value="tianijng"/>
            <property name="num" value="002"/>
        </bean>
    </property>
</bean>
​

4.3 集合类型赋值

01 数组

array标签+value标签:

<property name="books">
    <array>
        <value>西游记</value>
        <value>红楼梦</value>
        <value>水浒传</value>
    </array>
</property>

02 List

list标签+value标签:

<property name="hobbys">
    <list>
        <value>玩游戏</value>
        <value>看电影</value>
    </list>
</property>
123456

03 Map

map标签+entry标签,entry也可以使用ref引用:

<property name="card">
    <map>
        <entry key="中行" value="001"/>
        <entry key="邮政" value="002"/>
        <entry key-ref="..." value-ref="...."/>
    </map>
</property>

04 Properties

props标签:

 <property name="info">
     <props>
         <prop key="学号">20190604</prop>
         <prop key="性别">男</prop>
         <prop key="姓名">小明</prop>
     </props>
 </property>
​

05 util名称空间

util名称空间可以创建集合类型的bean,以便别的地方引用。

头文件约束:

xmlns:util="http://www.springframework.org/schema/util"
​
xsi:schemaLocation= "http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.1.xsd"
<!--util名称空间 提取出通用的集合-->
​
    <util:list id="myList">
        <value>玩游戏</value>
        <value>看电影</value>
    </util:list>
<!--使用ref直接引用util提取出来的集合id即可-->
    <bean id="student05" class="com.xiao.bean.Student">
        <property name="hobbys" ref="myList"/>
    </bean>
123456789

4.4 级联属性赋值

propetry标签中的name标签,可以使用级联属性,修改属性的属性,但是原来属性的值会被修改。

<bean id="address01" class="com.xiao.bean.Address">
     <property name="name" value="beijing"/>
     <property name="num" value="001"/>
</bean>
​
<bean id="student02" class="com.xiao.bean.Student">
    <property name="address" ref="address01"/>
    <!--将address01中的num属性进行了修改-->
    <property name="address.num" value="00005"/>
</bean>
​

4.5 继承实现配置信息重用

指定parent属性为要重用的bean的id值,不写的属性就沿用,也可以重写定义属性

<bean id="person01" class="com.xiao.bean.Person">
    <property name="lastName" value="zhangsan"/>
    <property name="age" value="20"/>
    <property name="email" value="zhangsan@163.com"/>
    <property name="gender" value="0"/>
    <property name="flag" value="true"/>
</bean>
​
    <!--parent:要重用的配置信息 -->
<bean id="person001" class="com.xiao.bean.Person" parent="person01">
    <!--单独修改name属性的值 -->
    <property name="lastName" value="zhang"/>
</bean>
​

还可以指定属性abstract=“true”,这样的bean只能被用来继承信息,不能获取实例,专门用于被继承。否则会报异常 BeanIsAbstractException

<bean id="person01" class="com.xiao.bean.Person" abstract="true">
    <!--使用property标签为Person对象的属性赋值
        name:指定属性名
        value:指定属性值
     -->
    <property name="lastName" value="zhangsan"/>
    <property name="age" value="20"/>
    <property name="email" value="zhangsan@163.com"/>
    <property name="gender" value="0"/>
    <property name="flag" value="true"/>
</bean>
 

5 bean的一些性质

5.1 bean之间依赖

多个bean的默认创建顺序,是按照配置顺序创建的。

<bean id="student" class="com.xiao.bean.Student"></bean>
<bean id="address" class="com.xiao.bean.Address"></bean>
<bean id="person" class="com.xiao.bean.Person"></bean>
123
Student创建了
Address创建了
Person创建了
123

可以用depends-on属性进行设置:

<bean id="student" class="com.xiao.bean.Student" depends-on="person,address"></bean>
<bean id="address" class="com.xiao.bean.Address"></bean>
<bean id="person" class="com.xiao.bean.Person"></bean>
123
Person创建了
Address创建了
Student创建了
123

5.2 bean的作用域scope

在bean配置中可以设置作用域属性scope:

  • singleton: 单例模式,是默认模式在容器启动完成之前就已经创建好对象保存在容器中了。 懒汉单例任何时候获取都是之前创建好的

  • prototype :多实例,原型模式,容器启动会不去创建,每次从容器中getBean获取的时候才会产生一个新对象,饿汉多实例,每次获取都会创建新对象

  • request:在web环境下,同一次请求创建一个bean实例(没用)

  • session:在web环境下,同一次会话创建一个bean实例(没用)

5.3 静态工厂与实例工厂

工厂模式:工厂帮我们创建对象;有一个专门帮我们创建对象的类,这个类就是工厂

静态工厂:工厂本身不用创建对象,通过静态方法调用,对象 = 工厂类.工厂方法名( )

实例工厂:工厂本身需要创建对象,先创建工厂对象,再通过工厂对象创建所需对象

工厂类 工厂对象 = new 工厂类();

工厂对象.getAirplane("plane01")

新建三个类Air、AirStaticFactory和AirInstanceFactory:

public class Air {
    private String name;
    private Double weight;
    private Double length;
    private Integer PersonNum;
    //get/set/toString...
}
public class AirStaticFactory {
    //提供一个static静态方法获取Air对象
    //AirStaticFactory.getAirPlane();
    public static Air getAir(String name){
        System.out.println("AirStaticFactory正在造飞机!");
        Air air = new Air();
        air.setName(name);
        air.setLength(100.0);
        air.setWeight(100.0);
        air.setPersonNum(200);
        return air;
    }
​
public class AirInstanceFactory {
​
    //提供一个方法获取Air对象
    //new AirStaticFactory().getAirPlane();
    
    public Air getAir(String name){
        System.out.println("AirInstanceFactory正在造飞机!");
        Air air = new Air();
        air.setName(name);
        air.setLength(100.0);
        air.setWeight(100.0);
        air.setPersonNum(200);
        return air;
    }
}
​

静态工厂:不需要创建工厂本身,class指定静态工厂的全类名,factory-method指定工厂方法

<!--静态工厂,不需要创建工厂本身,class指定静态工厂的全类名-->
<!--直接创建会创建AirStaticFactory,我们要告诉bean创建getAir对象 ,factory-method,用constructor-arg有参构造器传参-->
<bean id="air01" class="com.xiao.AirStaticFactory" factory-method="getAir">
    <constructor-arg name="name" value="林青霞"/>
</bean>
1234
//获取到的就是Air的实例
Air air01 = ioc.getBean("air01",Air.class);

实例工厂:先创建示例工厂本身,再创建对象,指定当前对象的创建需要哪个工厂factory-bean和哪个方法factory-method

 <!--实例工厂,需要先创建示例工厂本身-->
<bean id="airInstanceFactory" class="com.xiao.AirInstanceFactory">
</bean>
<!--指定当前对象的创建需要哪个工厂和哪个方法,不需要指定class了,factory-bean当前兑现刚创建那个工厂实例
 factory-method指定使用哪个工厂方法-->
<bean id="air02" factory-bean="airInstanceFactory" factory-method="getAir">
    <constructor-arg name="name" value="张学友"/>
</bean>
​
Air air02 = ioc.getBean("air02",Air.class);
​

5.4 自定义工厂

实现了FactoryBean接口的类,是Spring可以认识的工厂类,Spring都认为是工厂,Spring会自动调用工厂方法创建对象。

public class MyFactoryBeanImpl implements FactoryBean<Air> {
    //传入泛型为要建造的对象Air 
    //工厂方法,Spring会自动调用这个方法来创建对象并返回
    //返回创建的对象Air air
    @Override
    public Air getObject() throws Exception {
        sout("myfactorybean帮你创建对象..")
        Air air = new Air();
        air.setName("zhangsan");
        return air;
    }
​
    //返回对象的类型,Spring会自动调用这个方法来确认创建的对象是什么类型
    @Override
    public Class<?> getObjectType() {
        return Air.class;
    }
​
    //是单例模式吗?
    @Override
    public boolean isSingleton() {
        return false;
    }
}
​

注册工厂对象,会自动调用工厂方法返回对象:

<!--注册工厂对象,会自动调用工厂方法返回对象-->
//MyFactoryBeanImpl会自动调用FactoryBean内getObject()方法返回创建Air对象
<bean id="air03" class="com.xiao.MyFactoryBeanImpl">
</bean>
​
Object air03 = ioc.getBean("air03");
//air03是个Air类
​

FactoryBean这种类型,无论单/多实例,IOC容器启动时不会创建实例,使用getBean时才会创建

5.5 bean的生命周期方法

可以为bean自定义一些生命周期方法,Spring在创建或销毁bean时调用。init-methoddestroy-method,不能有参数。

IOC容器中注册的bean:

  • 单实例bean:容器启动的时候就会创建好,容器关闭也会销毁创建的bean

    (容器启动)构造器 —> 初始化方法 —> (容器关闭)销毁方法

  • 多实例bean:获取的时候才去创建

    (容器启动)构造器 —> 初始化方法 ,容器关闭不会调用bean的销毁方法

在Air类中新增两个方法:

public class Air {
    private String name;
    private Double weight;
    private Double length;
    private Integer PersonNum;
​
    public void destroy(){
        System.out.println("销毁方法被调用了!");
    }
​
    public void init(){
        System.out.println("初始方法被调用了");
    }
}
<bean id="air04" class="com.xiao.Air" init-method="init" destroy-method="destroy"> </bean>

5.6 bean的后置处理器

定义一个类实现BeanPostProcessor接口,其中两个方法postProcessBeforeInitializationpostProcessAfterInitialization会在调用初始化方法前后调用。需要注册这个实现类

即使没有定义初始化方法,这两个方法也会被调用。

public class MyBeanPostProcessor implements BeanPostProcessor {
​
    /**
     * 前置处理器,在初始化方法之前调用
     * @param bean 传递过来的,将要初始化的bean
     * @param beanName
     * @return 经该方法处理之后可以返回一个新的bean
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("【"+beanName+"】将要调用初始化方法了..BeforeInitialization..这个bean是这样的:+【"+bean+"】");
        return bean;
    }
​
    /**
     * 后置处理器,在初始化方法之后调用
     * @param bean
     * @param beanName
     * @return 经该方法处理后返回给IOC容器保存的bean
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("【"+beanName+"】初始化方法调用完了..AfterInitialization..这个bean是这样的:+【"+bean+"】");
        return bean;
    }
}
​
<bean id="air04" class="com.xiao.Air" init-method="init" destroy-method="destroy">
</bean>
<bean id="myBeanPostProcessor" class="com.xiao.MyBeanPostProcessor"/>
123

结果:

【air04】将要调用初始化方法了..BeforeInitialization..这个bean是这样的:+【Air{name='null', weight=null, length=null, PersonNum=null}】
初始方法被调用了
【air04】初始化方法调用完了..AfterInitialization..这个bean是这样的:+【Air{name='null', weight=null, length=null, PersonNum=null}】

6. bean的装配

6.1 Spring管理连接池

配置C3P0的数据库连接池,注册一个ComboPooledDataSource对象即可

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="root"/>
    <property name="password" value="root"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis"/>
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
</bean>
​

6.2 引入外部配置文件 *

单实例:数据库连接池,一个项目一个池

首先新建一个数据库连接池的配置文件db.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root

需要用到context命名空间:

 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd"
  

使用context:property-placeholder location=" ... "标签导入数据库配置文件db.properties,就可以用$取出对应的属性了:

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
​
<context:property-placeholder location="classpath:db.properties"/>
 

【一个小坑】:Spring内部已经定义过一个username了,${username}就是系统的用户名,所以这里定义的是jdbc.username

6.3 基于XML的自动装配

自动装配是Spring满足bean依赖的一种方式。Spring会在上下文中自动寻找,并给bean自动装配属性。

Spring中的三种装配方式:

  1. 在xml显示配置

  2. 在Java中显示配置

  3. 隐式的自动装配bean

bean标签中设置autowire属性:

<bean id="air05" class="com.xiao.Air" autowire="byName" ></bean>
1
  • autpwire="default/no":不自动装配

  • autpwire="byName":按照名字,以属性名作为id去容器中找到这个组件,为其赋值;如果找不到就装配null

  • autpwire="byType":按照类型,以属性的类型作为查找依据去容器中找到这个组件,为其赋值,该类型必须只有一个,否则会报异常NoUniqueBeanDifinetionException;如果找不到就装配null

  • autpwire="construction":按照构造器进行赋值:先按照有参构造器的参数类型进行装,如果没有就直接为组件装配null即可;如果按照类型有多个,就会把参数名作为id继续匹配,匹配到就自动装配,匹配不到就装配null。不会报错

7. 注解开发 *

7.1 不同层组件

  1. 通过给bean上添加注解,可以快速的将bean加入到IOC容器中。创建Dao、Service、Controller层所需要用到的注解:

  2. 某个类添加上任何一个注解都能快速的将这个组件加入到ioc容器管理中

    • @Component:组件,放在类上,将某个类注册到Spring中,id是类名首字母小写。相当于:<bean id=".." class="..">

    • @Repository:Dao持久化层

    • @Service:Service业务逻辑层 BookService

    • @Controller:Controller控制器层。控制器:控制网站跳转逻辑Servlet

  3. 还需要告诉Spring,自动扫描加了注解的组件:添加context名称空间,<context:component-scan base-package="com.xiao"/>。还需要有AOP包的依赖。idea不用配置?

  4. 组件的id默认是类名首字母小写,作用于默认是单例,可以修改。

    ioc.getBean("id"); id=bookDao

  5. @Repository("bookdaohaha") id改写成bookdaohaha

    默认是类名首字母小写,但可以改写

@Repository(value = "book")
@Scope(value = "prototype")
public class BookDao {
​
}
​
  • @Value:注入值,注入基本数据类型和String类型数据

  • @Scope:标注作用域。singleton, prototype…

    细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值是可以不写。

7.2 context扫描包的配置

指定要扫描的包:

<context:component-scan base-package="com.xiao"/>

指定扫描包时指定排除一些不要的组件:

<context:component-scan base-package="com.xiao">
    <!--指定排除不要的组件-->
    <context:exclude-filter type="..." expression="..."/>
</context:component-scan>
  • type="annotation":按照注解进行排除,expression属性中指定要排除的注解的全类名

  • type="assignable":按照类名进行排除,expression属性中指定要排除的类的全类名

只扫描进入指定的组件,默认都是全部扫描进来,use-default-filters需要设置为false:

<context:component-scan base-package="com.xiao" use-default-filters="false">
    <context:include-filter type="..." expression="..."/>
</context:component-scan>
123

7.3 Autowired自动装配 *Autowired

1. 基本使用

直接在成员上添加@Autowired完成自动装配。

Dao层:

@Repository
public class BookDao {
    //声明一个方法
    public void savaBook() {
        System.out.println("保存了一本书!");
    }
}
 

Service层,使用注解@Autowired完成成员BookDao的自动装配,调用dao层的方法:

@Service
public class BookService {
    //使用@Autowired完成成员BookDao的自动装配
    @Autowired   
    private BookDao bookDao;
​
    public void sava() {
        sout("bookservice正在调用bookdao保存图书");
         bookDao.savaBook();
    }
}

 

Controller层,使用注解@Autowired完成成员BookService的自动装配,调用service层的方法:

https://blog.csdn.net/qq_40126686/article/details/109728660 servlet

通俗来讲servlet专门用来接收客户端的请求,专门接收客户端的请求数据,然后调用底层service处理数据并生成结果 浏览器http请求------》tomcat服务器-------》到达servlet-----》执行doget,dopost方法----》返回数据

@Controller
public class BookServlet {
​
    @Autowired
    private BookService bookService;
​
    public void doGet() {
        sout("bookservlet正在调用bookservice保存图书");
         bookService.sava();
    }
}
 
public class IOCTest{
    ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    @Test
    public void test02(){
        ioc.getBean(BookServlet = ioc.getBean(BookServlet.class));
        bookServlet.doGet();
    }
}
​
bookservlet正在调用bookservice保存图书
bookservice正在调用bookdao保存图书
保存了一本书

 

02 Autowired的执 行流程

@Autowired:Spring会自动的为这个属性赋值,一定是去容器找这个属性对应的组件

@Autowired可以直接用在属性上,执行流程:

  1. 首先按照类型去容器中找对应的组件,如果找到一个就赋值,找不到就抛异常;

    @Autowired
    private BookService bookService 
    变量名:bookService
  2. 如果有多个类型(bookService,bookServiceExt)匹配时,会使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功,找不到就报错。

  3. 结合注解@Qualifer,指定一个id,让Spring别使用变量名为id:在自动按照类型注入的基础之上,再按照指定的bean的id去查找。它在给字段注入时不能独立使用,必须和@Autowired一起使用;但是给方法参数注入时,可以独立使用。

@Autowired标注的属性如果找不到就会报错,可以指定required属性,找不到就自动装配null

 @Autowired(required = false)

03 注解加在方法上

@Autowired:也可以使用在set方法上,执行流程跟上面一样;

@Qualifer:还可以用在方法的参数,指定按照哪个id去装配。指定一个名为id,让spring别使用变量名为id

@Nullable:标记的属性可以null

 

@Service
public class BookService {
​
​
    private BookDao bookDao;
​
    @Autowired
    private void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
​
    public void read() {
        this.bookDao.readBook();
    }
}

04 @Resource

@Resource:直接按照Bean的id注入,是Java自带的注解。执行流程:

j2ee:java的标准

Autowired:是spring的标准

Resouce扩展性更强,因为是java的标准

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

  2. 如果指定了name,则从上下文中查找id匹配的bean进行装配,找不到则抛出异常

  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常

  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;

7.4 Spring的单元测试

使用Spring的单元测试,不需要用ioc.getBean()来获取组件了,直接Autowired组件,Spring自动装配

导入依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.0.RELEASE</version>
    <scope>test</scope>
</dependency>
​
<!--Junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

添加注解:

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

  • @RunWith:指定用哪种驱动进行单元测试,默认是junit,这里指定用Spring的单元测试模块来执行标了@Test注解的测试方法

/*
 *@ContextConfiguration:指定Spring配置文件的位置
 *@RunWith:指定用哪种驱动进行单元测试,默认是junit,这里指定用Spring的单元测试模块来执行标了@Test注解的测试方法
 *
 */
@ContextConfiguration(locations = "classpath:ApplicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class Test02 {
    @Autowired
    private BookController bookController;
​
    @Test
    public void test01() {
        this.bookController.one();
    }
}

@Autowired好处:不用ioc.getBean();获取组件了,直接Autowired组件为我们自动装配

 

11

 

 

Spring(二)----动态代理、AOP

Spring基础知识学习笔记(二),内容包括:

  1. 代理模式:静态代理和动态代理

  2. AOP实现:注解实现+配置文件实现

  3. 切面、通知、切入点、切入点表达式

  4. 环绕通知

OOP:(Object Oriented Programming) 面向对象编程。

AOP:(Aspect Oriented Programming) 面向切面编程,基于OOP基础之上的编程思想,

在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运算。

应用场景:计算器运行计算方法的时候进行日志记录,不推荐直接在方法内部,修改维护麻烦。

日志记录:系统的辅助功能;

业务逻辑:核心功能; 二者耦合了

希望:在核心功能运行期间,系统的辅助功能自己动态的加上。

参考视频:

B站 尚硅谷雷丰阳大神的Spring、Spring MVC、MyBatis课程

1. 代理模式

1.1 静态代理

静态代理角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现

  • 真实角色 : 被代理的角色

  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .

  • 客户 : 使用代理角色来进行一些操作

案例:房东有房子,交给中介代理,客户直接找中介,中介在租房前后带客户看房子和收中介费。

租房接口Renet:

//抽象角色:租房接口
public interface Rent {
   public void rent();
}

真实角色房东:Host,实现了Rent接口,可以出租房子

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

代理角色Proxy:

public class Proxy implements Rent {
    
    private Host host;
    //无参
    public Proxy() {
    }
    //有参(参数host定义)
    public Proxy(Host host) {
        this.host = host;
    }
​
    @Override
    public void rent() {
        //中介在出租房屋前带客户看房子
        seeHouse();
        this.host.rent();
        //中介在出租房屋后收中介费
        fare();
    }
    
    public void seeHouse() {
        System.out.println("带客户看房子");
    }
    
    public void fare() {
        System.out.println("收中介费");
    }
}

客户Client,找中介租房:

public class Client {
​
    public static void main(String[] args) {
        //房东
        Host host = new Host();
        //中介来代理房东
        Proxy proxy = new Proxy(host);
        //客户找中介,中介出租房屋
        proxy.rent();
    }
}

结果:

带客户看房子
房屋出租!
收中介费

静态代理的好处:

  • 使得真实角色更加纯粹,不再去关注一些公共的事情

  • 公共的业务由代理来完成,实现了业务的分工

  • 公共业务发生扩展时变得更加集中和方便

缺点 :

  • 类多了 , 多了代理类 , 工作量变大了 ,开发效率降低

1.2 动态代理

作用:日志记录,解耦

动态代理的角色和静态代理的一样 ,区别是动态代理的代理类是动态生成的 ,静态代理的代理类是提前写好的。

动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

  • 基于接口的动态代理----JDK动态代理,代理对象和被代理对象唯一能产生的关联就是实现了同一个接口。如果目标对象没有实现任何接口,是无法为其创建代理对象的。

  • 基于类的动态代理–cglib

  • java字节码 javassist

JDK动态代理需要两个核心类:Proxy代理和InvocationHandler调用处理程序。

Proxy:

Proxy.newProxyInstance()方法为目标对象创建代理对象,返回代理对象。三个参数:

  • ClassLoader loader:和被代理对象使用相同的类加载器。

  • Class<?>[] interfaces:和被代理对象具有相同的行为。实现相同的接口。

  • InvocationHandler:如何代理,方法执行器。

InvocationHandler

调用其invoke()方法,执行被代理对象的任何方法,都会经过该方法,三个参数:被代理对象、方法、参数

  • Object proxy:被代理的对象,给jdk使用,任何时候都不用动

  • Method method:当前将要执行的目标对象方法

  • Object[] args:执行方法的参数

    calculator.getClass().getClassLoader();
    Class<?>[] interfaces = calculator.getClass().getInterfaces();
    InvocationHandler h = new InvocationHandler(){
    //匿名实现
        @Override
        public Object invoke(Object proxy,Method method ,Object[]args )
            throws Throwable{
            //反射执行目标方法 calculator被代理人
            Object result = method.invoke(calculator,args);
            return result;
        }
    }

     

代码实现:

  1. 定义一个出租房子的接口Rent

  2. 房东类实现Rent,具有出租房子的功能

  3. 定义一个类实现InvocationHandler接口,来创建动态代理对象,增强功能

  4. 动态代理对象调用方法

/**
@Description: 定义一个类实现InvocationHandler接口,来创建动态代理对象
 */
public class ProxyInvocationHandler implements InvocationHandler {
    private Rent rent;
    
    //设置要代理的接口
    public void setRent(Rent rent) {
        this.rent = rent;
    }
​
    //声明一个生成代理类的方法
    public Object getProxy() {
        //Proxy.newProxyInstance()传入三个参数:类加载器,类实现的接口,InvocationHandler对象
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }
​
    //处理实例,并返回结果
    @Override
    public Object invoke 
        //先看房
        seeHouse();
        //使用反射机制invoke方法,传入被代理的接口和参数。使用真实对象的方法
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }
​
    public void seeHouse() {
        System.out.println("中介带看房子");
    }
    public void fare(){
        System.out.println("中介收费");
    }
}
/**
 * @Description:测试类
 */
public class ProxyTest {
    public static void main(String[] args) {
        //真实角色
        Host host = new Host();
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
​
        //传入要代理的接口
        proxyInvocationHandler.setRent(host);
​
        //获得代理对象
        Rent proxy = (Rent) proxyInvocationHandler.getProxy();
        //代理对象使用真实对象的方法,方法被增强了
        proxy.rentHouse();
    }
}
​
​

1.3 动态代理实现日志功能

  1. 定义一个Calculator接口,声明加减乘除方法

  2. 定义一个MyCalculator类实现Calculator接口,完成方法体

  3. 定义一个生成代理对象的类CalculatorProxy,获取代理对象

  4. 重写InvocationHandler的invoke方法,在执行目标方法前后,添加相应的日志输出,也可以处理异常信息

Calculator接口:

public interface Calculator {
​
    //加减乘除方法
    public int add(int i, int j);
    public int subtract(int i, int j);
    public int multiply(int i, int j);
    public int divide(int i, int j);
​
}
123456789

MyCalculator类:

public class MyCalculator implements Calculator {
    @Override
    public int add(int i, int j) {
        return i + j;
    }
​
    @Override
    public int subtract(int i, int j) {
        return i - j;
    }
​
    @Override
    public int multiply(int i, int j) {
        return i * j;
    }
​
    @Override
    public int divide(int i, int j) {
        return i / j;
    }
}
 

日志工具类LogUtils:

public class LogUtils {
    //执行前
    public static void before(Method method,Object... args) {
        System.out.println("【"+method.getName()+"】方法开始执行了,用的参数列表是【"+ Arrays.asList(args)+"】");
    }
    //执行后
    public static void after(Method method,Object result) {
        System.out.println("【"+method.getName()+"】方法执行完成了,计算结果是【"+ result+"】");
    }
    //出现异常
    public static void exception(Method method,Exception e) {
        System.out.println("【"+method.getName()+"】方法出现异常了,异常信息是:"+e.getCause());
    }
    //方法结束
    public  static void end(Method method) {
        System.out.println("【"+method.getName()+"】方法最终结束了");
    }
​
}
​

生成代理对象的类CalculatorProxy:

public class CalculatorProxy {
​
    /**
     * Proxy.newProxyInstance()
     * 为传入的参数对象创建一个动态代理对象
     * @param calculator 被代理的对象
     * @return
     */
    public static Calculator getProxy(Calculator calculator) {
​
        Object proxy = Proxy.newProxyInstance(calculator.getClass().getClassLoader(), calculator.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * @param proxy 代理对象,给JDK使用的
                     * @param method 当前将要执行的目标对象的方法
                     * @param args 参数
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = null;
​
                        try {
                            //目标方法执行前
                            LogUtils.before(method,args);
                            System.out.println("动态代理要帮你执行方法!");
                            //利用反射执行目标方法
                            result = method.invoke(calculator, args);
​
                            //目标方法执行后
                            LogUtils.after(method,result);
                        } catch (Exception e) {
                           //目标方法出现异常
                            LogUtils.exception(method,e);
                        } finally {
                            //目标方法结束后
                            LogUtils.end(method);
                        }
                        //返回值必须返回出去,外界才能拿到真正执行后的返回值
                        return result;
                    }
                });
​
        //返回代理对象
        return (Calculator) proxy;
    }
}

测试:

public class CalculatorTest {
​
    @Test
    public void test(){
        Calculator calculator = new MyCalculator();
        Calculator proxy = CalculatorProxy.getProxy(calculator);
        proxy.add(1,2);
        proxy.divide(2,0);
    }
}
12345678910

结果:

【add】方法开始执行了,用的参数列表是【[1, 2]】
动态代理要帮你执行方法!
【add】方法执行完成了,计算结果是【3】
【add】方法最终结束了
【divide】方法开始执行了,用的参数列表是【[2, 0]】
动态代理要帮你执行方法!
【divide】方法出现异常了,异常信息是:java.lang.ArithmeticException: / by zero
【divide】方法最终结束了

动态切入:将某段代码(日志)动态(不写死在业务逻辑中)的切入到指定方法(加减乘除)的指定位置(方法的开始、结束)进行运行

代理对象和被代理对象唯一能产生的关联就是实现同一接口,如果没实现任何接口,是无法为他创建代理对象;

Spring实现AOP功能,底层就是动态代理

 

2. AOP

AOP:(Aspect Oriented Programming) 面向切面编程,将某段代码动态的切入到指定方法的指定位置(方法的开始、结束、异常…)。

使用场景:

  • 加日志保存到数据库

  • 做权限验证

  • 做安全检查

  • 做事务控制

2.1 几个专业术语

在这里插入图片描述

  • 横切关注点:与业务逻辑无关的,但是需要关注的部分,就是横切关注点,方法的开始、返回、异常、结束等。

  • 切面(ASPECT)类:在上面例子中相当于自己定义的一个日志工具类。

  • 通知(Advice):切面必须要完成的工作,是类中的一个方法。

  • 目标(Target):被通知对象。

  • 代理(Proxy):向目标对象应用通知之后创建的对象。

  • 连接点(JointPoint):每一个方法的每一个位置都是一个连接点

  • 切入点(PointCut):切面通知执行的 “地点”,即真正需要执行日志记录的地方

  • 切入点表达式:在众多连接点中选出我们感兴趣的地方

2.2 注解实现步骤

需要AOP织入,要导入依赖:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency> 
try{@Before
    method.invoke(obj,args);
    @AfterReturning
}catch(e){
    @AfterThrowing
}finally{
    @After
}

 

步骤:

  1. 将目标类和切面类(封装了通知方法的类)加入到IOC容器中,注解@Component,配置文件开启context:component-scan包扫描

  2. 告诉Spring到底哪个是切面类,在类上注解@Aspect

  3. 告诉Spring切面中的方都是何时何地运行,方法上注解

    通知注解

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

    • @After:在目标方法之后运行;后置通知

    • @AfterReturning:在目标方法正常返回之后;返回通知

    • @AfterThrowing:在目标方法抛出异常之后;异常通知

    • @Around:环绕通知

  4. 在注解中写切入点表达式:execution(访问权限符 返回值类型 方法全类名(参数表))

  5. 配置文件中开启基于注解的AOP功能

    AOP名称空间头文件

    xmlns:aop="http://www.springframework.org/schema/aop"
    
    xsi:schemaLocation="http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"
    123
    <!--开启注解支持-->
    <aop:aspectj-autoproxy/>
    12

代码实现:

目标类:

@Component
public class MyCalculator implements Calculator {
...
}
1234

切面类:

@Aspect
@Component
public class LogUtils {
    //执行前
    @Before("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))")
    public static void before() {
        System.out.println("方法开始执行了");
    }
    //执行后
    @AfterReturning("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))")
    public static void after() {
        System.out.println("方法执行完成了");
    }
    //出现异常
    @AfterThrowing("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))")
    public static void exception() {
        System.out.println("方法出现异常了");
    }
    //方法结束
    @After("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))")
    public  static void end() {
        System.out.println("方法最终结束了");
    }
}

测试,获取到目标对象的bean,执行方法

public class AopTest {
​
    ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml");
​
    @Test
    public void test() {
        //注意这里是根据接口类型获取的
        Calculator bean = ioc.getBean(Calculator.class);
        System.out.println(bean);//com.xiao.MyProxy02.MyCalculator@3c9bfddc
        System.out.println(bean.getClass());//class com.sun.proxy.$Proxy22
        bean.add(1,2);
    }
​
}
1234567891011121314

配置文件:

<?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">
    
    <!--包扫描-->
    <context:component-scan base-package="com.xiao.MyProxy02"/>
​
    <!--开启注解支持-->
    <aop:aspectj-autoproxy/>
​
</beans>
​

2.3 注解实现的几个细节

01 获取组件

IOC容器中保存的是组件的代理对象。ioc.getBean()中使用的接口类型,也可以用id名

Calculator bean = ioc.getBean(Calculator.class);
System.out.println(bean);//com.xiao.MyProxy02.MyCalculator@3c9bfddc
System.out.println(bean.getClass());//class com.sun.proxy.$Proxy22
123

02 cglib

<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

cglib可以为没有实现接口的组件创建代理对象,通过本类类型或者id名获取到:

class com.xiao.MyProxy02.MyCalculator$$EnhancerBySpringCGLIB$$5ef61d8e
1

03 切入点表达式的写法

固定格式:execution(访问权限符 返回值类型 方法全类名(参数表)),表达式中支持 && 、||、 !

"execution(* *.*(..))":表示任意返回值类型,任意包下的任意类的任意方法,任意参个数

通配符:

  • * 可以匹配一个或多个字符;匹配一个参数;匹配一层路径;权限位置不写就行

  • .. 匹配任意多个参数,任意类型参数,任意多层路径

04 通知方法的执行顺序

正常执行:Before →方法执行 →After → AfterReturning(正常返回)

出现异常:Before →方法执行 →After → AfterThrowing

05 拿到目标方法的详细信息

从JoinPoint对象中可以拿到方法的详细信息,joinPoint.getArgs(),joinPoint.getSignature()

也可以接收异常和返回值,需要自己传入对应的参数Object result、Exception exception,并且要告诉Spring指定返回值returning ,指定异常throwing

    //执行前
    @Before("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))")
    public static void before(JoinPoint joinPoint) {
        System.out.println("【"+ joinPoint.getSignature().getName()+"】方法开始执行了,用的参数列表是【"+ Arrays.asList(joinPoint.getArgs())+"】");

    }
    //执行后
    @AfterReturning(value = "execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))",returning = "result")
    public static void after(JoinPoint joinPoint,Object result) {
        System.out.println("【"+ joinPoint.getSignature().getName()+"】方法执行完成了,执行结果是【"+ result +"】");
    }
    //出现异常
    @AfterThrowing(value = "execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))",throwing = "exception")
    public static void exception(JoinPoint joinPoint,Exception exception) {
        System.out.println("【"+joinPoint.getSignature().getName()+"】方法出现异常了,异常信息是:"+exception);
    }
    //方法结束
    @After("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))")
    public  static void end(JoinPoint joinPoint) {
        System.out.println("【"+joinPoint.getSignature().getName()+"】方法最终结束了");
    }
123456789101112131415161718192021

06 抽取可重用的切入点表达式

自定义一个没有返回值和参数的方法,加上@Pointcut注解,声明切入点表达式,别的地方可以直接使用其方法名进行引用

@Pointcut("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))")
public static void myPoint(){
​
}
​
//执行前
@Before("myPoint()")
public static void before(JoinPoint joinPoint) {
...
}
12345678910

07 环绕通知

@Around:就是利用反射调用目标方法,可以在其中定义环绕前置、环绕返回、环绕异常和环绕后置通知。环绕通知是优先于普通通知执行的。

环绕通知只作用在自己的切面内。

@Around("myPoint()")
public Object myAround(ProceedingJoinPoint point) throws Throwable {
    //获取参数
    Object[] args = point.getArgs();
    //获取方法名
    String name = point.getSignature().getName();
    Object proceed = null;
​
    try {
        // @Before
        System.out.println("【环绕前置通知】..【" + name + "】方法开始,用的参数列表是" + Arrays.asList(args));
        //就是利用反射调用目标方法,类似于method.invoke(obj,args)
        proceed = point.proceed(args);
        // @AfterReturning
        System.out.println("【环绕返回通知】..【" + name + "】方法返回,返回值是" + proceed);
    } catch (Exception e) {
        // @AfterThrowing
        System.out.println("【环绕异常通知】..【" + name + "】方法出现异常,异常信息是" + e);
        //为了让外界知道这个异常,将其抛出
         throw new RuntimeException(e);
    } finally {
        // @After
        System.out.println("【环绕后置通知】..【" + name + "】方法结束");
    }
    //反射调用后的返回值也一定返回出去
    return proceed;
}
123456789101112131415161718192021222324252627

结果:

【环绕前置通知】..【add】方法开始,用的参数列表是[1, 2]
【add】方法开始执行了,用的参数列表是【[1, 2]】
【环绕返回通知】..【add】方法返回,返回值是3
【环绕后置通知】..【add】方法结束
【add】方法最终结束了
【add】方法执行完成了,执行结果是【3】
123456

执行顺序:

(环绕前置 —> 普通前置) —> 目标方法执行 —> 环绕正常返回/出现异常 —> 环绕后置 —> 普通后置 —> 普通返回或者异常

08 多切面情况

执行顺序按照类名顺序,前置1–>前置2–>目标方法 -->后置2–>后置1

在这里插入图片描述

在切面上使用@Order注解,给一个int值,值越小,优先级越高

2.4 配置文件实现

在容器中注册bean,相当于@component

<aop:config>:进行配置。

<aop:aspect ref="...">:`指定谁是切面类,相当于`@Aspet

<aop:pointcutid="..." expression="...":指定切入点和切入表达式

<aop:before method="..." pointcut-ref="..." >:指定怎么切入,切在哪里,相当于@Before等,该标签中也可以指定返回值、异常等信息。

<!--注册bean-->
<bean id="logUtils" class="com.xiao.MyProxy02.LogUtils"/>
<bean id="myCalculator" class="com.xiao.MyProxy02.MyCalculator"/>
​
<aop:config>
    <!--自定义切面aspect,ref:要引用的类-->
  <aop:aspect ref="logUtils">
      <!--切入点-->
      <aop:pointcut id="point" expression="execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))"/>
      <!--前置-->
      <aop:before method="before" pointcut-ref="point"/>
      <!--返回-->
      <aop:after-returning method="after" pointcut-ref="point" returning="result" />
      <!--异常-->
      <aop:after-throwing method="exception" pointcut-ref="point" throwing="exception"/>
      <!--后置-->
      <aop:after method="end" pointcut-ref="point"/>
  </aop:aspect>
</aop:config>
  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值