Spring学习笔记

Spring框架

1.1Spring优势

  • 方便解偶,简化开发
  • AOP编程的支持
  • 声明式事务的支持
  • 方便程序测试
  • 方便集成各种优秀框架
  • 降低JAVA EE API使用难度

1.2程序的耦合和解耦

耦合的原则:耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

在这里插入图片描述

1.3Spring两大核心

在这里插入图片描述

core container是spring的核心之一,spring的任何其他部分运行都离不开它。1.

1.4 工厂模式创建对象代替new来解耦

  • 创建BeanFactory工厂类, 获取properties对象,生成getBean方法获取bean对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static {
    try {
    //实例化对象
    props = new Properties();
    //获取Properties文件流对象
    InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); //类加载器获取文件流而非new一个file流解耦
    props.load(in);
    } catch (IOException e) {
    throw new ExceptionInInitializerError("配置文件初始化失败!");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static Object getBean(String beanName) throws Exception {
    String beanPath = props.getProperty(beanName); //从配置文件获取bean
    Object bean = null;
    try {
    bean = Class.forName(beanPath).getConstructor().newInstance(); //反射获取bean对象(service实现类,Dao实现类)
    System.out.println(beanPath);
    }catch (Exception e){
    e.printStackTrace();
    }
    return bean;
    }
  • 在resources文件下创建bean.properties(xml和properties都可)

    1
    2
    accountService=com.fyw.service.Impl.AccountServiceImpl
    accountDao = com.fyw.dao.Impl.AccountDaoImpl
  • 表现层调用业务层,业务层调用持久层

    1
    private IAccountDao accountDao = (IAccountDao) 	BeanFactory.getBean("accountDao");		//业务层获取Dao层
1
2
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
as.saveAccount(); //表现层获取业务层,通过业务层的saveAccount调用持久层方法

代码优化

  • BeanFactory

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    private static Map<String,Object> beans;
    //定义一个Map存放要创建的对象,称之为容器
    static {
    try {
    //实例化对象
    props = new Properties();
    //获取Properties文件流对象
    InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
    props.load(in);
    //实例化容器
    beans = new HashMap<String,Object>(); //!!!
    //取出配置文件中所有的key
    Enumeration keys = props.keys(); //!!!
    //遍历枚举
    while (keys.hasMoreElements()){
    //取出每个key
    String key = keys.nextElement().toString();
    //根据key获取balue
    String beanPath = props.getProperty(key); //!!!
    //反射创建对象
    Object value = Class.forName(beanPath).getConstructor().newInstance(); //!!!
    //把key和value放入容器中
    beans.put(key,value);
    }
    } catch (Exception e) {
    throw new ExceptionInInitializerError("配置文件初始化失败!");
    }

用hashmap当作容器,将配置文件中的值用key,value存放。

  • ServiceImpl

    1
    2
    3
    4
    5
    6
    7
    public void saveAccount(){
    IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao"); //!!!

    accountDao.saveAccount();
    System.out.println(i);
    i++;
    };

定义变量i,在用户层调用5次,实现从多例模式转换为单例模式

注意!可能会出现空指针异常,主要是因为配置文件写的类和实际类加载顺序可能不同(先加载dao再加载service???)

  • 解决方法:将

    1
    IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");

    Service调用dao的代码从类中移到方法中,、

  • 结果截图

    image-20210125145233201

调用5次均使用同一service实现类对象,而非重新创建。

1.5IOC(控制反转)和Spring中的IOC

  • 控制反转:(Inversion of Control)把创建对象的权利交给框架,是框架的重要特征。它包括依赖注入和依赖查找。
  • ioc的作用:降低程序间的耦合。

1.5.1环境搭建

  • 添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.9.RELEASE</version>
    </dependency>
  • 配置xml文件

1
2
3
4
5
6
7
8
9
10
<?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">

<!--把对象的创建交给spring来管理-->
<bean id="accountService" class="com.fyw.service.Impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.fyw.dao.Impl.AccountDaoImpl"></bean>
</beans>
  • 从容器获取对象

    1
    2
    3
    4
    5
    //获取容器对象
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    //根据id获取Bean对象
    IAccountService as = (IAccountService) ac.getBean("accountService");
    IAccountDao ad = (IAccountDao) ac.getBean("accountDao");

1.5.2ApplicationContext三个常用实现类

  • ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话加载不了。(更常用)
  • FileSystemXmlApplicationContext:可以加载磁盘任意路径下的配置文件(必须有访问权限)
  • AnnotationConfigApplicationContext:用于读取注解创建容器

1.5.3ApplicationContext和BeanFactory区别

  • ApplicationContext在构建核心容器时,创建对象采取的时立即加载的方式,也就是说,读取完配置文件马上就创建配置文件中配置的对象。(单例对象适用)(智能的接口,可以根据对象的单例和多例,采用延迟还是立即加载)

    1
    2
    3
    4
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    //根据id获取Bean对象 !!!在此步获取配置文件后就创建了对象
    IAccountService as = (IAccountService) ac.getBean("accountService");
    IAccountDao ad = (IAccountDao) ac.getBean("accountDao");
  • BeanFactory在构建核心容器时,创建对象采取的时延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候就真正地创建对象。(多例对象适用)

1
2
3
4
5
//根据BeanFactory创建对象
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService as = (IAccountService) factory.getBean("accountService");
//!!! 在此步创建对象

1.6Spring对bean的管理细节

1.6.1创建bean的三种方式

  • 1>使用默认构造函数创建

    • 在spring配置文件bean.xml中使用标签,配以id和class属性,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,如果此时类中没有默认构造函数,则对象无法创建

    • org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.fyw.service.Impl.AccountServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.fyw.service.Impl.AccountServiceImpl.()

      无默认构造函数则会报错

  • 2>使用普通工厂中的方法创建对象(使用某个类的方法创建对象,并存入spring容器)

    • ```xml

      1
      2
      3
      4
      5

      - 3>使用普通工厂中的静态方法创建对象(使用某个类的方法创建对象,并存入spring容器)

      - ```xml
      <bean id="accountService" class="com.fyw.factory.staticFactory" factory-method="getAccountService"></bean>

      最后创建的是 staticFactory 对象,用 accountService来取。且getAccountService方法为静态

  • 注意:

    • 方式2/3针对的是当我们需要用到某个类的方法返回值,但是这个类在jar包中(.class文件),我们就采用这两种方式来处理。

1.6.2bean的细节之作用范围

  • bean标签中的 scope属性
    • 作用:用于指定bean的作用范围
    • 可选值:(常用单例和多例)
      • singleton:单例模式(默认)
      • prototype:多例模式
      • request:作用于web应用取指范围
      • session:作用于web应用的会话范围
      • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时它就是session

1.6.3bean的生命周期

  • 单例生命周期
    • 出生:容器创建时(创建容器即创建类时,通过默认构造创建对象可知)
    • 活着:容器活着时
    • 死亡:容器销毁时
    • 总结:单例对象声明周期和容器相同
  • 多例声明周期
    • 出生:当我们使用对象时,spring框架为我们创建
    • 活着:对象在使用中
    • 死亡:当对象长时间不用且没有别的对象引用时,由java垃圾回收器回收(Spring无法判别是否无用)

1.7Spring中的依赖注入

  • 概念:在当前类需要用到其他类的对象时,交给spring来提供,只需要在配置文件中说明依赖关系的维护方式,就称为依赖注入。
  • 可以注入三种数据:
    • 基本类型和String类型
    • 其他bean类型(在配置文件中或者注解配置过的bean)
    • 复杂类型/集合类型
  • 注入的方式
    • 默认构造函数注入
    • 用set方法
    • 使用注解提供/复杂类型的注入

1.7.1构造函数注入

  • 写有参构造函数覆盖无参,从而无默认构造函数
1
2
3
4
5
6
//如果是经常变化的数据,则不适用于注入的方式
public AccountServiceImpl(String name, Integer age, Date date) {
this.name = name;
this.age = age;
this.date = date;
}
  • 修改bean.xml

    属性标签中的属性:

    ​ type:用于指定构造函数中的某个或者某些参数的数据类型

    ​ index:用于指定构造函数的位置,从0开始

    ​ name:用于指定构造函数中的参数的名字,更为常用

    ​ value:为基本数据类型和String类型注入具体的内容

    ​ ref:用于指定其他的bean类型,它指定的是spring容器中出现过的bean对象

    ​ 优势:必须将构造函数中所有的参数进行内容注入

    ​ 弊端:改变了创建bean对象的方式,在我们创建对象时如果用不到这些数据也必须提供

1
2
3
4
5
6
<bean id="accountService" class="com.fyw.service.Impl.AccountServiceImpl"
scope="singleton" init-method="init" destroy-method="destroy">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="date" value="1999/08/15"></constructor-arg>
</bean>

1.7.2set方法注入

  • 生成set方法

  • 配置property标签

    1
    2
    3
    4
    5
    6
    <bean id="accountService" class="com.fyw.service.Impl.AccountServiceImpl"
    scope="singleton" init-method="init" destroy-method="destroy">
    <property name="name" value="张三"></property>
    <property name="age" value="18"></property>
    <property name="date" value="1999/08/15"></property>
    </bean>

    标签中的属性: name:用于注入时所调用的方法名 value:为基本数据类型和String类型注入具体的内容 ref:用于指定其他的bean类型,它指定的是spring容器中出现过的bean对象 优势:可以为我们想要注入的数据注入内容,更加灵活

    弊端:如果某个成员必须有值,则获取对象可能set方法没有执行(多线程问题)

    • 注意:

      • 不可以提供构造函数,因为构造函数注入和set方法注入是不兼容的

      • ​ 经过测试,如果有构造函数,赋值注入操作仍然可以成功,但是是在类加载完成后。

        image-20200306160102586
      • 经过测试,如果构造注入和set注入同时存在也不会报错,而是在加载类时值为构造注入,加载完成后为set注入。

        image-20200306160442824

1.7.3使用注解提供/复杂类型的注入

用于给List集合注入的标签: list、 array、 set 用于给map集合注入的标签: map、 props 结构相同,标签可以互换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>

<property name="myStr">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>

<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>

<property name="myMap">
<map>
<entry key="TestA" value="AAA"></entry>
<entry key="TestB" value="BBB"></entry>
<entry key="TestC" value="CCC"></entry>
</map>
</property>

1.7.4补充

  • java的三层架构:SSH
    • Struts(表示层)
      • Struts是一个表示层框架,主要作用是界面展示,接收请求,分发请求
    • Spring(业务层)
      • Spring是一个业务层框架,是一个整合的框架,能够很好地黏合表示层与持久层
    • Hibernate(持久层)
      • Hibernate是一个持久层框架,它只负责与关系数据库的操作。
  • 编译期错误
    • 程序开发过程的各个阶段都可能发生错误,可以将程序设计中的错误分成五类:
      • 编译期错误
      • 连接错误
      • 运行期错误
      • 逻辑性错误
      • 警告性错误

排错是非常困难的,有可能花费很长的时间。程序设计的目标应该是避免出现太多的问题。对减少 排错能有所帮助的技术包括:好的设计、好的风格、边界条件测试、合理性检查、限制全局数据等等

  • Ioc到底干了一件什么事?
    • 层层封装,形成模块化,良好代码环境,便于维护。维护不用修改源码,只操作bean就可。
    • 当我们不想new一个对象,在获得容器的控制权后,利用镜像的方式创建操作的类对象,调用类方法。

2.1spring基于注解的IOC以及IoC的案例

2.1.1注解的分类

  • 用于创建对象的

    • 和xml中的标签功能一样
    • @component
      • @Controller :一般用于表现层
      • @Service: 一般用于业务层
      • @Repository:一般用于持久层
      • 作用与Component一样,主要用于区分不同层级
      • 把当前 类对象存入spring容器中
  • 用于注入数据的

    • @AutoWired
      • 作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
      • 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
      • 如果Ioc容器中有多个类型匹配(例如多个类实现一个接口)时:先找变量名,没有则报错,有的话按照变量名匹配注入。
      • 出现位置:可以是变量上,也可以是方法上
      • 细节:在使用注解注入时,set方法就不是必须的了。
      • img
    • @Qualifier
      • 按照类中注入的基础上再按照名称注入。它在给类成员注入时不能单独使用。(和Autowired搭配使用,相当于解决了AutoWired的多个匹配情况)
      • 属性value:用于指定注入bean的id
    • @Resource可以不用和AutoWired搭配而单独指定id(name=)

    @AutoWired+@Qualifier(“accountDao”)==@Resource(“accountDao”)

    以上三个表达式都只能注入其他bean类型的数据 ,而基本类型和String类型无法使用上述注解实现,而用@value注解。

    @Resource需要添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.1</version>
    </dependency>

    集合的注入只能通过xml来实现。

    • @Value
      • 作用:注入基本数据和String类型数据
      • 属性:value:用于指定数据的值。它可以使用Spring中的SpEL(Spring中的EL表达式)
      • SpEL写法:${表达式}
    • 标签中的标签功能一样
  • 用于改变作用域的

    • @Scope
      • 指定bean的作用范围,value:Singleton 和prototype
    • 和scope标签功能一样
  • 和生命周期相关的(了解)

    • @PreDestroy
      • 用于指定销毁方法
    • @PostConstruct
      • 用于指定初始化方法
    • 和init-method和destroy-method功能一样
  • 新注解

    • @Configuration
    • 指定当前类是一个配置类(替换bean.xml)
    • @ComponentScan
      • 用于通过注解时指定要扫描的包
    • @Bean
      • 把当前方法的返回值存入Spring容器中
      • name:用于指定bean的id
      • 如果使用注解配置方法时,如果有参数,Spring则会在容器中查找有没有可用的bean对象,方式与AutoWired一样
    • @import
      • 当有多个配置类时在AnnotationApplicationContext的参数的类中导入即可

    ↓↓↓↓↓配置类替代beanxml↓↓↓↓↓↓↓

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Configuration
    @ComponentScan("com.fyw")
    public class SpringConfiguration {

    @Bean("runner")
    public QueryRunner createQueryRunner(DataSource ds){
    return new QueryRunner(ds);
    }

    @Bean
    public DataSource createDataSource() throws PropertyVetoException {
    try {
    ComboPooledDataSource ds = new ComboPooledDataSource();
    ds.setDriverClass("com.mysql.jdbc.Driver");
    ds.setJdbcUrl("jdbc:mysql://localhost:3306/db_mybatis");
    ds.setUser("root");
    ds.setPassword("root");
    return ds;
    }catch (Exception e){
    throw new RuntimeException("数据源配置失败!");
    }
    }
    }

2.1.2流程

  1. 当使用注解注入时需要在xml文件中告知要扫描的包

  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 基于注解的配置-->
    <context:component-scan base-package="com.fyw"></context:component-scan>
    </beans>
  3. 在需要注解的类前加入注解

2.1.3junit单元测试

  • 导入spring整合的junit的jar包
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
  • 使用junit提供的注解把原有的main方法替代,换成spring提供的@Runwith()
  • 告知Spring运行器,spring和ioc的创建是基于xml还是注解的,并且说明位置
    • @ContextConfiguration()
      • locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
      • classes:指定注解类所在的位置

注意:当使用spring5.x版本时,要求junit的jar包必须是4.12及以上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml") //如果是注解则设置 classes=配置类
public class accountServiceTest {
@Autowired
private IAccountService as;
}
@Test
public void testFindAll() {
// ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
// IAccountService as = ac.getBean("accountService",IAccountService.class);
List<Account> accounts = as.findAllAccount();
for (Account account:accounts){
System.out.println(account);
}

}

3第三天 动态代理与AOP

3.1.1动态代理

  • 特点
    • 字节码随用随创建,随用随加载
  • 作用
    • 不修改源码的基础上对方法增强
  • 分类
    • 基于接口的动态代理

    • 基于子类的动态代理

3.1.2基于接口的动态代理

  • 涉及类:Proxy

  • 提供者:JDK官方

  • 如何创建代理对象:使用Proxy类中的newProxyInstance方法

    • newProxyInstance参数:

      • ClassLoader:类加载器:用于加载代理对象字节码的,和被代理对象使用相同的类加载器。(固定写法)

      • class[]:字节码数组:用于让代理对象和被代理对象有相同的方法(固定写法)

      • InvocationHandler:用于提供增强的代码:我们一般都是写一个接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写

        • ```java @Override public Object invoke(Object proxy, Method method, Object[] args)
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31

          - 参数:<font color=deepblue>proxy</font>:代理对象的引用

          - <font color=deepblue>method</font>:当前执行的方法

          - <font color=deepblue>args</font>:当前执行方法所需的参数

          - <font color=deepblue>return</font>:和被代理对象有相同的返回值

          - 创建代理对象的要求:被代理类最少实现一个接口,没有则不能使用

          ```java
          public static void main(String[] args) {
          Producer producer = new Producer();
          IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(Producer.class.getClassLoader(), Producer.class.getInterfaces(),
          new InvocationHandler() {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          //提供增强方法
          Object retuenValue = null;
          //1.获取方法执行的参数
          Float money = (Float) args[0];
          //2.判断当前方法是不是销售
          if ("sellProduct".equals(method.getName())){
          System.out.println("拿到了80%");
          retuenValue = method.invoke(producer,money*0.8f);
          }
          return retuenValue;
          }
          });
          proxyProducer.sellProduct(1000f);

3.1.3基于子类的动态代理

  • 涉及类:Enhancer
  • 提供者:第三方cglib库
  • 如何创建代理对象:使用Enhancer类中的create方法
    • class:被代理对象的字节码
    • callback:用于增强的代码,一般写的是该接口的子接口实现类:MethodInterceptor
  • 创建代理对象的要求:被代理类不能是最终类(即final修饰的类 )

3.1.4AOP(Aspect Oriented Programming)

  • 概念:面向切面编程

  • 作用:程序运行期间,不修改源码对已有方法进行增强

  • 实现方式:动态代理

  • 相关术语

    • JoinPoint(连接点)(接口中的方法)

      • 被拦截到的点,在spring中,点指的是方法。
    • PointCut(切入点)(代理中被增强的方法)

      • 是指我们要对哪些JoinPoint进行拦截的定义
    • Advice:通知

      • 前置通知,后置通知,异常通知,最终通知,环绕通知

    • Target:目标对象

      • 代理的目标对象
    • Weaving(织入)

      • 增强应用到目标对象来创建新的代理对象的过程(例如加入事务支持的过程)
    • Proxy(代理)

      • 一个类被AOP增强后产生的一个结果代理类
    • Aspect(切面)

      • 切入点和通知的结合
image-20200430170741809

3.2基于xml的AOP编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置IOc,把Service配置进来-->
<bean id="accountService" class="com.fyw.Service.impl.AccountServiceImpl"></bean>

<!-- 基于AOP的配置
1.配置通知bean
2.使用aop:config表明开始aop的位置
3.使用aop:aspect配置切面
id:给切面提供一个唯一标识
ref:指定通知类bean的id
4.在aop:aspect内部使用对应的标签配置通知类型
5.pointcut指定切入点表达式,表示对哪些方法进行增强
execution(表达式)
表达式: 访问修饰符 返回值 包名.包名...类名.方法()
-->
<!-- 配置logger-->
<bean id="logger" class="com.fyw.utils.Logger"></bean>
<aop:config>
<!-- 配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="printLog" pointcut="execution(public void com.fyw.Service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
1
2
3
4
5
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency> 用于解析execution表达式

pointcut指定切入点表达式,表示对哪些方法进行增强 execution(表达式) 表达式: 访问修饰符 返回值 包名.包名...类名.方法()

  • ​ 全通配写法:

    • * *..*.*(..) :代理所以切入点

    • 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.

      • * *.*.*.AccountServiceImpl . saveAccount( )) 包名可以使用. .表示当前包及其子包

      • * *..AccountServiceImpl. saveAccount( )

      • 类名和方法名都可以使用*来实现通配

    • 通常写法:

      • *** com.fyw.service/impl.*.*(..)**
1
2
3
4
5
6
7
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.fyw.Service.impl.*.*(..))"/>
<!-- 配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="printLog" pointcut-ref="pt"></aop:before>
</aop:aspect>
</aop:config>

配置通知,注意:aop:pointcut必须要在配置切面前配置

3.2.2配置环绕通知

  • 问题: 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
  • 分析: 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
  • 解决: Spring框架为我们提供了一个接口: ProceedingJoinPoint。 该接口有一个方法proceed(),此方法就相当于明确调用切入点方法|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue =null;
try {
Object[] args=pjp.getArgs();//得到方法执行所需参数
System.out.println("前置通知");
rtValue = pjp.proceed(args); //调用业务层方法
System.out.println("后置通知");
return rtValue;
}catch (Throwable e){
System.out.println("异常通知");
throw new RuntimeException(e);
}finally {
System.out.println("最终通知");
}
}

环绕通知配置后可以手动指定其他通知出现的地方,从而不需要在bean.xml中配置其他通知

3.3基于注解的AOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.fyw"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component("logger")
@EnableAspectJAutoProxy //使用此注解可以省掉bean.xml
@Aspect //表示当前类是一个切面类
public class Logger {

@Pointcut("execution(* com.fyw.Service.impl.*.*(..))")
private void pt(){}; !!!必须是方法
/**
* 打印日志,并且让其在切入点方法执行前执行
*/
@Before("pt()") !!!必须有括号
public void printLog(){
System.out.println("开始记录日志");
}
//@Around("pt()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue =null;
try {
Object[] args=pjp.getArgs();//得到方法执行所需参数
System.out.println("前置通知");
rtValue = pjp.proceed(args); //调用业务层方法
System.out.println("后置通知");
return rtValue;
}catch (Throwable e){
System.out.println("异常通知");
throw new RuntimeException(e);
}finally {
System.out.println("最终通知");
}
}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!