Spring全面详解(学习总结)IOC&AOP 附:源码笔记

目录

        🚀不想看简介可以直接从第二部分开始 , 博客笔记源码在最下面,  需要搭配源码看的话自行下载即可


1.Spring简介

1.2 Spring模块 

2.IOC控制反转

2.1 概念

2.2 入门案例

2.3 Bean的常用属性配置

2.3.1 id

2.3.2 class

2.3.3 scope

3.DI依赖注入

3.1 set方法注入

3.2 有参构造注入

3.3 复杂类型属性注入

3.4 特殊值处理

3.5 内部bean

3.6 级联属性赋值

4.Lombok

①导入依赖

②增加注解

5.SPEL

6.配置文件

6.1 读取properties文件

6.2 引入Spring配置文件

7. Bean

7.1 bean的配置

7.1.1 name属性

7.1.2 lazy-init

7.1.3 init-method

7.1.4 destroy-method

7.1.5 factory-bean&factory-method

8. SpringIOC注解开发

注解开发准备工作

8.1 IOC相关注解

8.1.1 @Component,@Controller,@Service ,@Repository

8.2 DI相关注解

8.2.1 @Value

8.2.2 @AutoWired

8.2.3 @Qualifier

8.3 xml配置文件相关注解

8.4 包含和排除

9. 如何选择

10. AOP面向切面编程

10.1 概念

10.2 快速入门

10.2.1 需求

10.2.2 准备工作

10.2.3 实现AOP

10.3 AOP核心概念

10.4 切点确定

10.4.1 切点表达式

1.4.2 切点函数@annotation

10.5 通知/增强分类

10.6 获取被增强方法相关信息

10.7 xml配置AOP

10.8 多切面顺序问题

10.9 AOP原理-动态代理

10.9.1 JDK动态代理

10.9.2 Cglib动态代理

10.9.3 总结

10.10 切换默认动态代理方式

11. Spring整合Junit

源码在这里 包含Spring整合Mybatis


1.Spring简介

        Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。

        目前是JavaEE开发的灵魂框架。他可以简化JavaEE开发,可以非常方便整合其他框架,无侵入的进行功能增强。

        Spring的核心就是 控制反转(IoC)面向切面(AOP)

  • 优良特性

    • 非侵入式

    • 控制反转(IOC)

      • Spring为我们提供的一个对象容器(Spring容器)

      • 对象的创建、初始化、维护、销毁都归Spring去管理

    • 依赖注入(DI)

      • 是控制反转的实现

    • 面向切面编程(AOP)

      • 动态代理

    • 容器

    • 组件化

      • 通过一些很简单的配置,让各个组件组合在一起,web应用

    • 一站式

      • 只使用Spring一个框架就可以完成web开发

        • 包含持久层框架(和MyBatis相同的功能,但是没有MyBatis强大)

      • SpringMVC就是Spring的产品

        • web端框架

1.2 Spring模块 

Spring框架分为五大模块:

. Core Container:核心容器

  • Beans提供 BeanFactory,它是一个工厂模式的复杂实现。
  • Core提供了框架的基本组成部分,包括IOC和DI功能。
  • Context建立在由核心和 Bean 模块提供的基础上,它是访问定义和配置的任何对象的媒介。ApplicationContext 接口是上下文模块的重点。
  • SpEL在运行时提供了查询和操作一个对象的强大的Spring表达式语言。

. AOP&Aspects:提供了面向切面编程的实现。

. DataAccess/Integration:提供了对数据访问/集成的功能。

. Web:提供了面向Web应用程序的集成功能。

. Test:提供了对JUnit 或 TestNG 框架的测试功能。

2.IOC控制反转

2.1 概念

        控制反转,之前对象的控制权在类手上,现在反转后到了Spring手上。

2.2 入门案例

①导入依赖

导入SpringIOC相关依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

②编写配置文件

        在resources目录下创建applicationContext.xml文件,文件名可以任意取。但是建议叫applicationContext。

内容如下:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--
        classs:配置类的全类名
        id:配置一个唯一标识
    -->
    <bean class="com.dabai.dao.impl.StudentDaoImpl" id="studentDao"  >
    </bean>


</beans>

③创建容器从容器中获取对象并测试

    public static void main(String[] args) {

//        1.获取StudentDaoImpl对象
        //创建Spring容器,指定要读取的配置文件路径
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从容器中获取对象
        StudentDao studentDao = (StudentDao) app.getBean("studentDao");
        //调用对象的方法进行测试
        System.out.println(studentDao.getStudentById(1));
    }

2.3 Bean的常用属性配置

2.3.1 id

bean的唯一标识,同一个Spring容器中不允许重复

2.3.2 class

全类名,用于反射创建对象

2.3.3 scope

scope主要有两个值:singleton和prototype

如果设置为singleton则一个容器中只会有这个一个bean对象。默认容器创建的时候就会创建该对象。

如果设置为prototype则一个容器中会有多个该bean对象。每次调用getBean方法获取时都会创建一个新对象。

3.DI依赖注入

依赖注入可以理解成IoC的一种应用场景,反转的是对象间依赖关系维护权。

3.1 set方法注入

在要注入属性的bean标签中进行配置。前提是该类有提供属性对应的set方法。

package com.sangeng.domain;

public class Student {

    private String name;
    private int id;
    private int age;

    private Dog dog;

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }

    public Student() {

    }

    public Student(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
    <bean class="com.sangeng.domain.Dog" id="dog">
        <property name="name" value="小白"></property>
        <property name="age" value="6"></property>
    </bean>

    <bean class="com.sangeng.domain.Student" id="student" >
        <!--
            name属性用来指定要设置哪个属性
            value属性用来设置要设置的值
            ref属性用来给引用类型的属性设置值,可以写上Spring容器中bean的id
        -->
        <property name="name" value="东南枝"></property>
        <property name="age" value="20"></property>
        <property name="id" value="1"></property>
        <property name="dog" ref="dog"></property>
    </bean>

3.2 有参构造注入

在要注入属性的bean标签中进行配置。前提是该类有提供对应的有参构造。

<!--使用有参构造进行注入-->
    <bean class="com.sangeng.domain.Student" id="student2" >
        <constructor-arg name="name" value="自挂东南枝"></constructor-arg>
        <constructor-arg name="age" value="20"></constructor-arg>
        <constructor-arg name="id" value="30"></constructor-arg>
        <constructor-arg name="dog" ref="dog"></constructor-arg>
    </bean>

3.3 复杂类型属性注入

实体类如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int age;
    private String name;
    private Phone phone;
    private List<String> list;
    private List<Phone> phones;
    private Set<String> set;
    private Map<String, Phone> map;
    private int[] arr;
    private Properties properties;
}

配置如下:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.sangeng.domain.Phone" id="phone">
        <property name="price" value="3999"></property>
        <property name="name" value="黑米"></property>
        <property name="password" value="123"></property>
        <property name="path" value="qqqq"></property>
    </bean>
    
    <bean class="com.sangeng.domain.User" id="user">
        <property name="age" value="10"></property>
        <property name="name" value="大队长"></property>
        <property name="phone" ref="phone"></property>
        <property name="list">
            <list>
                <value>三更</value>
                <value>西施</value>
            </list>
        </property>

        <property name="phones">
            <list>
                <ref bean="phone"></ref>
            </list>
        </property>

        <property name="set">
            <set>
                <value>setEle1</value>
                <value>setEle2</value>
            </set>
        </property>

        <property name="map">
            <map>
                <entry key="k1" value-ref="phone"></entry>
                <entry key="k2" value-ref="phone"></entry>
            </map>
        </property>

        <property name="arr">
            <array>
                <value>10</value>
                <value>11</value>
            </array>
        </property>

        <property name="properties">
            <props>
                <prop key="k1">v1</prop>
                <prop key="k2">v2</prop>
            </props>
        </property>
    </bean>
</beans>

3.4 特殊值处理

  • 字面量

    • 普通的数据(String/Integer/Double...)

  • null值

    • <null></null>

  • 出现的特殊的符号

        若字面值中包含特殊字符,可以使用<![CDATA[   ]]>把字面值包裹起来

<bean id="book" class="com.spring.beans.Book">
    <property name="id" value="4"></property>
    <property name="title">
        <value><![CDATA[<<西游记>>]]></value>
    </property>
    <property name="author" value="吴承恩"></property>
    <property name="sales">
        <null></null>
    </property>
</bean>

3.5 内部bean

<!--
    内部bean
-->
<bean id="emp04" class="com.dabai.Employee">
    <property name="id" value="104"/>
    <!--想使用内部bean-->
    <property name="department" >
        <!--内部bean,只能被emp04使用,其他的位置是引用不到的,id是无用,写不写都行-->
        <bean id="dept02" class="com.atguigu.Department">
            <property name="id" value="2"/>
            <property name="name" value="村委会"/>
        </bean>
    </property>
</bean>

3.6 级联属性赋值

<!--级联属性赋值-->
<bean id="emp05" class="com.dabai.Employee">
    <property name="id" value="105"/>
    <!--级联属性
        department被emp01/02/03都引用了
      
    -->
    <property name="department" ref="department"/>
    <!--引用级联属性后,在当前对象内可以对级联对象的属性进行赋值操作
        要操作department对象的name或者id属性的值
        前提:成功的引入的外部的bean
    -->
    <property name="department.name" value="小卖部"/>
    <property name="department.id" value="100"/>
    <!-- 这里修改了department.id的值那emp01/02/03的值也会改变-->
</bean>

4.Lombok

①导入依赖

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>

②增加注解

@Data //根据属性生成set,get方法
@NoArgsConstructor //生成空参构造
@AllArgsConstructor //生成全参构造
public class Phone {
    private double price;
    private String name;
    private String password;
    private String path;

}

5.SPEL

我们可以再配置文件中使用SPEL表达式。写法如下:

        <property name="age" value="#{20+3}"/>
        <property name="car" value="#{car}"/>

注意:SPEL需要写到value属性中,不能写到ref属性。

6.配置文件

6.1 读取properties文件

我们可以让Spring读取properties文件中的key/value,然后使用其中的值。

①设置读取properties

在Spring配置文件中加入如下标签:指定要读取的文件的路径。

<context:property-placeholder location="classpath:filename.properties">

其中的classpath表示类加载路径下。

我们也会用到如下写法:classpath:*.properties 其中的 * 表示文件名任意。

注意:context命名空间的引入是否正确

②使用配置文件中的值

在我们需要使用的时候可以使用${key}来表示具体的值。注意要再value属性中使用才可以。例如:

<property name="propertyName" value="${key}"/>

6.2 引入Spring配置文件

我们可以在主的配置文件中通过import标签的resource属性,引入其他的xml配置文件

<import resource="classpath:applicationContext-book.xml"/>

7. Bean

7.1 bean的配置

7.1.1 name属性

我们可以用name属性来给bean取名。例如:

    <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource" name="dataSource2,dataSource3">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

获取的时候就可以使用这个名字来获取了

    public static void main(String[] args) {

        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        DruidDataSource dataSource = (DruidDataSource) app.getBean("dataSource3");
        System.out.println(dataSource);

    }

7.1.2 lazy-init

可以控制bean的创建时间,如果设置为true就是在第一次获取该对象的时候才去创建。

    <bean class="com.alibaba.druid.pool.DruidDataSource" lazy-init="true"  id="dataSource" name="dataSource2,dataSource3">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

7.1.3 init-method

可以用来设置初始化方法,设置完后容器创建完对象就会自动帮我们调用对应的方法。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {

    private String name;
    private int id;
    private int age;
	//初始化方法
    public void init(){
        System.out.println("对学生对象进行初始化操作");
    }

}
<bean class="com.sangeng.domain.Student" id="student" init-method="init"></bean>

注意:配置的初始化方法只能是空参的。

7.1.4 destroy-method

可以用来设置销毁之前调用的方法,设置完后容器销毁对象前就会自动帮我们调用对应的方法。

<bean class="com.sangeng.domain.Student" id="student"  destroy-method="close"></bean>
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {

    private String name;
    private int id;
    private int age;

    public void init(){
        System.out.println("对学生对象进行初始化操作");
    }

    public void close(){
        System.out.println("对象销毁之前调用,用于释放资源");
    }
}

注意:配置的方法只能是空参的。

7.1.5 factory-bean&factory-method

当我们需要让Spring容器使用工厂类来创建对象放入Spring容器的时候可以使用factory-bean和factory-method属性。

7.1.5.1 配置实例工厂创建对象

配置文件中进行配置

    <!--创建实例工厂-->
    <bean class="com.sangeng.factory.CarFactory" id="carFactory"></bean>
    <!--使用实例工厂创建Car放入容器-->
    <!--factory-bean 用来指定使用哪个工厂对象-->
    <!--factory-method 用来指定使用哪个工厂方法-->
    <bean factory-bean="carFactory" factory-method="getCar" id="car"></bean>

7.1.5.2 配置静态工厂创建对象

配置文件中进行配置

    <!--使用静态工厂创建Car放入容器-->
    <bean class="com.sangeng.factory.CarStaticFactory" factory-method="getCar" id="car2">        
    </bean>

8. SpringIOC注解开发

注解开发准备工作

        为了简化配置,Spring支持使用注解代替xml配置。

        如果要使用注解开发必须要开启组件扫描,这样加了注解的类才会被识别出来。Spring才能去解析其中的注解。

<!--启动组件扫描,指定对应扫描的包路径,该包及其子包下所有的类都会被扫描,加载包含指定注解的类-->
<context:component-scan base-package="com.sangeng"/>

8.1 IOC相关注解

8.1.1 @Component,@Controller,@Service ,@Repository

        上面4个注解都是加到类上的。

        他们都可以起到类似bean标签的作用。可以把加了该注解类的对象放入Spring容器中。

        实际再使用时选择任意一个都可以。但是后3个注解是语义化注解。

        如果是Service类要求使用@Service

        如果是Dao类要求使用@Repository

        如果是Controllerl类(SpringMVC中会学习到)要求使用@Controller

        如果是其他类可以使用@Component

8.2 DI相关注解

        如果一个bean已经放入Spring容器中了。那么我们可以使用下列注解实现属性注入,让Spring容器帮我们完成属性的赋值。

8.2.1 @Value

        主要用于String,Integer等可以直接赋值的属性注入。不依赖setter方法,支持SpEL表达式。

@Service("userService")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserServiceImpl implements UserService {
    private UserDao userDao;
    @Value("199")
    private int num;
    @Value("Ren大白")
    private String str;
    @Value("#{19+3}")
    private Integer age;


    public void show() {
        userDao.show();
    }
}

8.2.2 @AutoWired

Spring会给加了该注解的属性自动注入数据类型相同的对象。

@Service("userService")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

required属性代表这个属性是否是必须的,默认值为true。如果是true的话Spring容器中如果找不到相同类型的对象完成属性注入就会出现异常。

8.2.3 @Qualifier

        如果相同类型的bean在容器中有多个时,单独使用@AutoWired就不能满足要求,这时候可以再加上@Qualifier来指定bean的名字从容器中获取bean注入。

    @Autowired
    @Qualifier("userDao2")
    private UserDao userDao;

注意:该直接不能单独使用。单独使用没有作用

8.3 xml配置文件相关注解

@Configuration

标注在类上,表示当前类是一个配置类。我们可以用注解类来完全替换掉xml配置文件。

注意:如果使用配置类替换了xml配置,spring容器要使用:AnnotationConfigApplicationContext

@Configuration
public class ApplicationConfig {
}

@ComponentScan

可以用来代替context:component-scan标签来配置组件扫描。

basePackages属性来指定要扫描的包。

注意要加在配置类上。

@Configuration
@ComponentScan(basePackages = "com.sangeng")//指定要扫描的包
public class ApplicationConfig {
}

@Bean

可以用来代替bean标签,主要用于第三方类的注入。

使用:定义一个方法,在方法中创建对应的对象并且作为返回值返回。然后在方法上加上@Bean注解,注解的value属性来设置bean的名称。

@Configuration
@ComponentScan(basePackages = "com.sangeng")
public class ApplicationConfig {

    @Bean("dataSource")
    public DruidDataSource getDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUsername("root");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_db");
        druidDataSource.setPassword("root");
        return druidDataSource;
    }

}

注意事项:如果同一种类型的对象在容器中只有一个,我们可以不设置bean的名称。

@PropertySource

可以用来代替context:property-placeholder,让Spring读取指定的properties文件。然后可以使用@Value来获取读取到的值。

使用:在配置类上加@PropertySource注解,注解的value属性来设置properties文件的路径。

然后在配置类中定义成员变量。在成员变量上使用@Value注解来获取读到的值并给对应的成员变量赋值。

@Configuration
@ComponentScan(basePackages = "com.sangeng")
@PropertySource("jdbc.properties")
public class ApplicationConfig {

    @Value("${jdbc.driver}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;


    @Bean
    public DruidDataSource getDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUsername(username);
        druidDataSource.setUrl(url);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }

}

注意事项:使用@Value获取读到的properties文件中的值时使用的是${key},而不是#{key}。

8.4 包含和排除

<context:component-scan base-package="com.dabai" use-default-filters="false">
        <!--设置排除
            context:exclude-filter  标签名
                type : 可以根据不同的类型排除
                    annotation:根据注解的类型排除
                    assignable:根据父接口或父类排除
                expression :
                    注解的全类名
                    父接口或者父类的全类名
            从com.dabai包下,扫描注解,将Repository这个注解排除掉
        -->
<!--        <context:exclude-filter type="assignable" expression="com.dabai.dao.UserDao"/>-->

        <!--设置包含
            context:include-filter:标签名
                条件:必须在context:component-scan上添加属性 use-default-filters="false"
                type : 可以根据不同的类型排除
                    annotation:根据注解的类型排除
                    assignable:根据父接口或父类排除
                expression :
                    注解的全类名
                    父接口或者父类的全类名
        -->
        <context:include-filter type="assignable" expression="com.dabai.service.UserService"/>

    </context:component-scan>

9. 如何选择

①SSM

自己项目中的类的IOC和DI都使用注解,对第三方jar包中的类,配置组件扫描时使用xml进行配置。

②SpringBoot

纯注解开发

10. AOP面向切面编程

10.1 概念

        AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。他是一种可以在不修改原来的核心代码的情况下给程序动态统一进行增强的一种技术。

        SpringAOP: 批量对Spring容器中bean的方法做增强,并且这种增强不会与原来方法中的代码耦合。

10.2 快速入门

10.2.1 需求

        要求让SpringAOP模块中service包下所有类的所有方法在调用前都输出:方法被调用了。

10.2.2 准备工作

①添加依赖

需要添加SpringIOC相关依赖和AOP相关依赖。

        <!--SpringIOC相关依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <!--AOP相关依赖-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>

②相关bean要注入容器中

开启组件扫描

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

加@Service注解

@Service
public class PhoneService {

    public void deleteAll(){
        System.out.println("PhoneService中deleteAll的核心代码");
    }
}
@Service
public class UserService {


    public void deleteAll(){
        System.out.println("UserService中deleteAll的核心代码");
    }
}

10.2.3 实现AOP

①开启AOP注解支持

使用aop:aspectj-autoproxy标签

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启组件扫描-->
    <context:component-scan base-package="com.sangeng"></context:component-scan>
    <!--开启aop注解支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

②创建切面类

创建一个类,在类上加上@Component和@Aspect

使用@Pointcut注解来指定要被增强的方法

使用@Before注解来给我们的增强代码所在的方法进行标识,并且指定了增强代码是在被增强方法执行之前执行的。

@Component
@Aspect
public class MyAspect {

//    用Pointcut注解中的属性来指定对哪些方法进行增强
    @Pointcut("execution(* com.sangeng.service.*.*(..))")
    public void pt(){}

    /*
        用@Before注解来指定该方法中是增强的代码,并且是在被增强方法执行前执行的
        @Before的属性写上加了@Pointcut注解的方法: 方法名()
    */
    @Before("pt()")
    public void methodbefore(){
        System.out.println("方法被调用了");
    }

}

10.3 AOP核心概念

  • Joinpoint(连接点):所谓连接点是指那些可以被增强到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

  • Pointcut(切入点):所谓切入点是指被增强的连接点(方法)

  • Advice(通知/ 增强):所谓通知是指具体增强的代码

  • Target(目标对象):被增强的对象就是目标对象

  • Aspect(切面):是切入点和通知(引介)的结合

  • Proxy (代理):一个类被 AOP 增强后,就产生一个结果代理类

10.4 切点确定

10.4.1 切点表达式

可以使用切点表达式来表示要对哪些方法进行增强。

写法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))

  • 访问修饰符可以省略,大部分情况下省略

  • 返回值类型、包名、类名、方法名可以使用星号* 代表任意

  • 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类

  • 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

execution(* com.sangeng.service.*.*(..))   表示com.sangeng.service包下任意类,方法名任意,参数列表任意,返回值类型任意
   
execution(* com.sangeng.service..*.*(..))   表示com.sangeng.service包及其子包下任意类,方法名任意,参数列表任意,返回值类型任意
    
execution(* com.sangeng.service.*.*())     表示com.sangeng.service包下任意类,方法名任意,要求方法不能有参数,返回值类型任意
    
execution(* com.sangeng.service.*.delete*(..))     表示com.sangeng.service包下任意类,要求方法不能有参数,返回值类型任意,方法名要求已delete开头 

1.4.2 切点函数@annotation

        我们也可以在要增强的方法上加上注解。然后使用@annotation来表示对加了什么注解的方法进行增强。

        写法:@annotation(注解的全类名)

定义注解如下

@Target({ElementType.METHOD})//该注解可以加在方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokeLog {
}

给需要增强的方法增加注解

@Service
public class PhoneService {

    @InvokeLog  
    public void deleteAll(){
        System.out.println("PhoneService中deleteAll的核心代码");
    }
}

切面类中使用@annotation来确定要增强的方法

@Component
@Aspect
public class MyAspect {

//    用Pointcut注解中的属性来指定对哪些方法进行增强
    @Pointcut("@annotation(com.sangeng.aspect.InvokeLog)")
    public void pt(){}

    /*
        用@Before注解来指定该方法中是增强的代码,并且是在被增强方法执行前执行的
        @Before的属性写上加了@Pointcut注解的方法: 方法名()
    */
    @Before("pt()")
    public void methodbefore(){
        System.out.println("方法被调用了");
    }
}

10.5 通知/增强分类

  • @Before:前置通知,在目标方法执行前执行

  • @AfterReturning: 返回后通知,在目标方法执行后执行,如果出现异常不会执行

  • @After:后置通知,在目标方法之后执行,无论是否出现异常都会执行

  • @AfterThrowing:异常通知,在目标方法抛出异常后执行

  • @Around:环绕通知,围绕着目标方法执行

理解不同通知执行时机。(下面的伪代码是用来理解单个通知的执行时机的,不能用来理解多个通知情况下的执行顺序。如果需要配置多个通知我们会选择使用Around通知,更加的清晰并且好用

public Object test() {
        before();//@Before 前置通知
        try {
            Object ret = 目标方法();//目标方法调用
            afterReturing();//@AfterReturning 返回后通知
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            afterThrowing();//@AfterThrowing 异常通知通知
        }finally {
            after();//@After 后置通知
        }
        return ret;
    }

环绕通知非常特殊,它可以对目标方法进行全方位的增强。

@Around("pt()")
    public void around(ProceedingJoinPoint pjp){
        System.out.println("目标方法前");
        try {
            pjp.proceed();//目标方法执行
            System.out.println("目标方法后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("目标方法出现异常");
        }finally {
            System.out.println("finally中进行增强");
        }
    }

10.6 获取被增强方法相关信息

        我们实际对方法进行增强时往往还需要获取到被增强代码的相关信息,比如方法名,参数,返回值,异常对象等。

        我们可以在除了环绕通知外的所有通知方法中增加一个JoinPoint类型的参数。这个参数封装了被增强方法的相关信息。我们可以通过这个参数获取到除了异常对象和返回值之外的所有信息。

   @Before("pt()")
    public void methodbefore(JoinPoint jp){
        Object[] args = jp.getArgs();//方法调用时传入的参数
        Object target = jp.getTarget();//被代理对象
        MethodSignature signature = (MethodSignature) jp.getSignature();//获取被被增强方法签名封装的对象
        System.out.println("Before方法被调用了");
    }

10.7 xml配置AOP

①定义切面类

public class MyAspect {


    public void before(JoinPoint joinPoint){
        System.out.println("before");
    }

//    @AfterReturning(value = "pt()",returning = "ret")
    public void afterReturning(JoinPoint joinPoint,Object ret){
        System.out.println("afterReturning:"+ret);
    }
//    @After("pt()")
    public void after(JoinPoint joinPoint){
        System.out.println("after");
    }

//    @AfterThrowing(value = "pt()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Throwable e){
        String message = e.getMessage();
        System.out.println("afterThrowing:"+message);
    }

    public Object around(ProceedingJoinPoint pjp){
        //获取参数
        Object[] args = pjp.getArgs();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Object target = pjp.getTarget();
        Object ret = null;
        try {
            ret = pjp.proceed();//目标方法的执行
            //ret就是被增强方法的返回值
            System.out.println(ret);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println(throwable.getMessage());
        }
//        System.out.println(pjp);
        return ret;
    }
}

②目标类和切面类注入容器

        在切面类和目标类上加是对应的注解。注入如果是使用注解的方式注入容器要记得开启组件扫描。

        当然你也可以在xml中使用bean标签的方式注入容器。

@Component//把切面类注入容器
public class MyAspect {
	//..。省略无关代码
}
@Service//把目标类注入容器
public class UserService {
	//..。省略无关代码
}

③配置AOP

<?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.sangeng"></context:component-scan>

    <!--配置AOP-->
    <aop:config>
        <!--定义切点-->
        <aop:pointcut id="pt1" expression="execution(* com.sangeng.service..*.*(..))"></aop:pointcut>
        <aop:pointcut id="pt2" expression="@annotation(com.sangeng.aspect.InvokeLog)"></aop:pointcut>
        <!--配置切面-->
        <aop:aspect ref="myAspect">
            <aop:before method="before" pointcut-ref="pt1"></aop:before>
            <aop:after method="after" pointcut-ref="pt1"></aop:after>
            <aop:after-returning method="afterReturning" pointcut-ref="pt1" returning="ret"></aop:after-returning>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt2" throwing="e"></aop:after-throwing>
        </aop:aspect>
    </aop:config>
</beans>

10.8 多切面顺序问题

        在实际项目中我们可能会存在配置了多个切面的情况。这种情况下我们很可能需要控制切面的顺序。

        我们在默认情况下Spring有它自己的排序规则。(按照类名排序)

        默认排序规则往往不符合我们的要求,我们需要进行特殊控制。

        如果是注解方式配置的AOP可以在切面类上加@Order注解来控制顺序。@Order中的属性越小优先级越高。

        如果是XML方式配置的AOP,可以通过调整配置顺序来控制。

        例如:下面这种配置方式就会先使用CryptAspect里面的增强,在使用APrintLogAspect里的增强

@Component
@Aspect
@Order(2)
public class APrintLogAspect {
    //省略无关代码
}
@Component
@Aspect
@Order(1)
public class CryptAspect {
    //省略无关代码
}

10.9 AOP原理-动态代理

        实际上Spring的AOP其实底层就是使用动态代理来完成的。并且使用了两种动态代理分别是JDK的动态代理和Cglib动态代理

10.9.1 JDK动态代理

        JDK的动态代理使用的java.lang.reflect.Proxy这个类来进行实现的。要求被代理(被增强)的类需要实现了接口。并且JDK动态代理也只能对接口中的方法进行增强。

public static void main(String[] args) {
        AIControllerImpl aiController = new AIControllerImpl();
        //使用动态代理增强getAnswer方法
        //1.JDK动态代理
        //获取类加载器
        ClassLoader cl = Demo.class.getClassLoader();
        //被代理类所实现接口的字节码对象数组
        Class<?>[] interfaces = AIControllerImpl.class.getInterfaces();
        AIController proxy = (AIController) Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() {
            //使用代理对象的方法时 会调用到invoke
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //proxy   是代理对象
                //method 是当前被调用的方法封装的Method对象
                //args   是调用方法时传入的参数
                //调用被代理对象的对应方法
                //判断 当前调用的是否是getAnswer方法
                if(method.getName().equals("getAnswer")){
                    System.out.println("增强");
                }
                Object ret = method.invoke(aiController, args);
                return ret;
            }
        });
        String answer = proxy.getAnswer("三连了吗?");
		System.out.println(answer);
    }

10.9.2 Cglib动态代理

        使用的是org.springframework.cglib.proxy.Enhancer类进行实现的。

public class CglibDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(AIControllerImpl.class);
        enhancer.setCallback(new MethodInterceptor() {
            //使用代理对象执行方法是都会调用到intercept方法
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //判断当前调用的方法是不是getAnswer方法 如果是进行增强
                if ("getAnswer".equals(method.getName())){
                    System.out.println("被增强了");
                }
                //调用父类中对应的方法
                Object ret = methodProxy.invokeSuper(o, objects);
                return ret;
            }
        });
        //生成代理对象
        AIControllerImpl proxy = (AIControllerImpl) enhancer.create();
//        System.out.println(proxy.getAnswer("你好吗?"));
        System.out.println(proxy.fortuneTelling("你好吗?"));
    }
}

10.9.3 总结

        JDK动态代理要求被代理(被增强)的类必须要实现接口,生成的代理对象相当于是被代理对象的兄弟。

        Cglib的动态代理不要求被代理(被增强)的类要实现接口,生成的代理对象相当于被代理对象的子类对象。

        Spring的AOP默认情况下优先使用的是JDK的动态代理,如果使用不了JDK的动态代理才会使用Cglib的动态代理。

10.10 切换默认动态代理方式

有的时候我们需要修改AOP的代理方式

我们可以使用以下方式修改:

如果我们是采用注解方式配置AOP的话:

设置aop:aspectj-autoproxy标签的proxy-target-class属性为true,代理方式就会修改成Cglib

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

如果我们是采用xml方式配置AOP的话:

设置aop:config标签的proxy-target-class属性为true,代理方式就会修改成Cglib

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

11. Spring整合Junit

①导入依赖

<!-- junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<!-- spring整合junit的依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>

② 编写测试类

在测试类上加上

@RunWith(SpringJUnit4ClassRunner.class)注解,指定让测试运行于Spring环境

@ContextConfiguration注解,指定Spring容器创建需要的配置文件或者配置类

@RunWith(SpringJUnit4ClassRunner.class)//让测试运行与Spring测试环境
@ContextConfiguration(locations = "classpath:配置文件1.xml")//设置Spring配置文件或者配置类
//@ContextConfiguration(classes = SpringConfig.class)
public class SpringTest {}

③注入对象进行测试

在测试类中注入要测试的对象,定义测试方法,在其中使用要测试的对象。

@RunWith(SpringJUnit4ClassRunner.class)//让测试运行与Spring测试环境
@ContextConfiguration(locations = "classpath:配置文件1.xml")//设置Spring配置文件或者配置类
//@ContextConfiguration(classes = SpringConfig.class)
public class SpringTest {
    
    // 想测哪个对象,就注入哪个对象
    @Autowired
    private UserService userService;
    
    //定义测试方法
    @Test
    public void testUserService() {
        userService.findById(10);
    }
    
}

源码在这里 包含Spring整合Mybatis

baidu 网盘链接:百度网盘 请输入提取码

提取码: 6666

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值