JavaWeb-Spring框架(1)

https://blog.csdn.net/a745233700/article/details/80959716

Spring框架概述

  • Spring框架是什么?

    Spring是分层的Java SE/EE应用 full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。

  • Spring的优势

    1. 方便解耦,简化开发: 通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
    2. AOP编程的支持: 通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
    3. 声明式事务的支持: 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
    4. 方便程序的测试: 可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情
    5. 方便集成各种优秀框架: Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
    6. 降低JavaEE API的使用难度: Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。

Spring体系结构

spring.JPG

IoC的概念和作用

1. 程序的耦合与工程模式解耦

  • 程序的耦合

    耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。

  • 工厂模式解耦

    在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。 那么,这个读取配置文件,创建和获取三层对象的类就是工厂

创建service和dao对象

1).需要一个配置文件配置service和dao
2).通过读取配置文件中的配置内容,反射创建对象

1.JPG

2.JPG

2. IoC的概念

  • IoC的概念:

    IoC(Inverse Of Control:控制反转):把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的术语。他包括依赖注入(Dependency Injection,DI)依赖查找(Dependency Lookup)

  • IoC的作用:

    消减计算机程序的耦合(解除代码间的依赖关系)

3. 使用Spring Ioc解决程序耦合

这里使用的案例是,账户的业务层和持久层的依赖关系解决。创建如下程序:

  • 创建持久层接口和实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
       
    /**
    * 账户的持久层接口
    */
    public interface IAccountDao {
    /**
    * 模拟保存账户
    */
    void saveAccount();
    }
    /**
    * 账户的持久层实现类
    */
    public class AccountDaoImpl implements IAccountDao {
    public void saveAccount(){
    System.out.println("保存了账户");
    }
    }
  • 创建业务层接口和实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
      
    /**
    * 账户业务层的接口
    */
    public interface IAccountService {
    /**
    * 模拟保存账户
    */
    void saveAccount();
    }
    /**
    * 账户的业务层实现类
    */
    public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao ;
    public AccountServiceImpl(){
    System.out.println("对象创建了");
    }
    public void saveAccount(){
    accountDao.saveAccount();
    }
    }

基于XML的配置

  1. 导入Spring jar包
  2. 在resources目录下创建任意.xml文件

    {% codeblock lang:java %}   
    > 	
    > 		 		   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">
    > 	
    > 		
    > 		
    > 		
    > 		 	{% endcodeblock %}
    
  3. 使用spring

    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
    32
    33
    34
    35
    36
       
    /**
    * 模拟一个表现层,用于调用业务层
    */
    public class Client {

    /**
    * 获取spring的Ioc核心容器,并根据id获取对象
    *
    * ApplicationContext的三个常用实现类:
    * ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
    * FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
    *
    * AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是明天的内容。
    *
    * 核心容器的两个接口引发出的问题:
    * ApplicationContext: 单例对象适用 采用此接口
    * 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
    *
    * BeanFactory: 多例对象使用
    * 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
    * @param args
    */
    public static void main(String[] args) {
    //1.获取核心容器对象
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    //ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\zhy\\Desktop\\bean.xml");
    //2.根据id获取Bean对象
    IAccountService as = (IAccountService)ac.getBean("accountService");
    IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);

    System.out.println(as);
    System.out.println(adao);
    as.saveAccount();
    }
    }

Spring基于XML的IOC细节

1. spring中工厂的类结构图

bean.jpg

  • BeanFactory 和ApplicationContext 的区别

    * BeanFactory 才是Spring 容器中的顶层接口。
    * ApplicationContext 是它的子接口。
    * BeanFactory 和 ApplicationContext 的区别:
        创建对象的时间点不一样。
        - ApplicationContext: 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
        - BeanFactory:它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
    
  • ApplicationContext 接口的实现类

    * ClassPathXmlApplicationContext:
        它是从类的根路径下加载配置文件 推荐使用这种
    * FileSystemXmlApplicationContext:
        它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
    * AnnotationConfigApplicationContext:
        当我们使用注解配置容器对象时,需要使用此类来创建spring 容器。它用来读取注解。
    

2. IOC中bean标签和管理对象细节

1)创建Bean的三种方式

第一种方式:使用默认无参构造函数

在Spring的配置文件中使用bean标签,配以id和class属性之后,没有其他属性标签时,根据默认无参构造函数来创建类对象。如果bean中没有默认无参构造函数,将会创建失败。

<bean id="accountService" class="bjtu.service.impl.AccountServiceImpl"/>

第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)

beanc2.JPG

第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)

beanc1.JPG

2)Bean的作用范围和生命周期

  • bean的作用范围

    bean标签的scope属性:   
        作用:用于指定bean的作用范围
        取值: 常用的就是单例的和多例的
            singleton:单例的(默认值)-一个应用只有一个对象的实例。它的作用范围就是整个引用。
            prototype:多例的-每次访问对象时,都会重新创建对象实例。
            request:作用于web应用的请求范围
            session:作用于web应用的会话范围
            global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
    
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>-->
    
  • bean的生命周期

    <!-- bean对象的生命周期
        单例对象
            出生:当容器创建时对象出生
            活着:只要容器还在,对象一直活着
            死亡:容器销毁,对象消亡
            总结:单例对象的生命周期和容器相同
        多例对象
            出生:当我们使用对象时spring框架为我们创建
            活着:对象只要是在使用过程中就一直活着。
            死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
    -->
    

3. Spring中的依赖注入

* 依赖注入:
    Dependency Injection
* IOC的作用:
    降低程序间的耦合(依赖关系)
* 依赖关系的管理:
    以后都交给spring来维护
    在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
* 依赖关系的维护:
    就称之为依赖注入。

*依赖注入:
   1. 能注入的数据:有三类
        1) 基本类型和String
        2) 其他bean类型(在配置文件中或者注解配置过的bean)
        3) 复杂类型/集合类型
   2. 注入的方式:有三种
        1) 第一种:使用构造函数提供
        2) 第二种:使用set方法提供
        3) 第三种:使用注解提供

1)构造函数注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;

public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday; }
@Override
public void saveAccount() {
ystem.out.println(name+","+age+","+birthday);
}
}
  • 使用的标签:constructor-arg
  • 标签出现的位置:bean标签的内部

    标签中的属性
        type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
        index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
        name:用于指定给构造函数中指定名称的参数赋值                                        常用的
        =============以上三个用于指定给构造函数中哪个参数赋值===============================
        value:用于提供基本类型和String类型的数据
        ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
    
    优势:
        在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
    弊端:
        改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
    
    -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="泰斯特"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>
    
    <!-- 配置一个日期对象 -->
    <bean id="now" class="java.util.Date"></bean>
    

2) set方法注入

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
    
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;

public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void setName(String name) {
this.name = name;
}

public void setAge(Integer age) {
this.age = age;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

@Override
public void saveAccount() {
ystem.out.println(name+","+age+","+birthday);
}
}
  • 涉及的标签:property
  • 出现的位置:bean标签的内部

    标签的属性
        name:用于指定注入时所调用的set方法名称
        value:用于提供基本类型和String类型的数据
        ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
    优势:
        创建对象时没有明确的限制,可以直接使用默认构造函数
    弊端:
        如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
    -->
    <bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
        <property name="name" value="TEST" ></property>
        <property name="age" value="21"></property>
        <property name="birthday" ref="now"></property>
    </bean>
    

3)注入集合

顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组,List,Set,Map,Properties。具体代码如下:

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
public class AccountServiceImpl implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
@Override
public void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
复杂类型的注入/集合类型的注入          
    *用于给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
32
33
34
35
36
37
38
39
40
41
42
   
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>

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

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

<property name="myMap">
<props>
<prop key="testC">ccc</prop>
<prop key="testD">ddd</prop>
</props>
</property>

<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
</bean>

Spring 基于注解的IoC配置

基于XML的IoC配置:

1
2
3
4
5
   
> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="" init-method="" destroy-method="">
> <property name="" value="" | ref=""></property>
> </bean>
>

环境构建

在使用注解进行IoC配置前,应更改项目的 xml (bean.xml)配置文件。 基于注解整合时,导入约束时需要多导入一个context名称空间下的约束。

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: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/context
> http://www.springframework.org/schema/context/spring-context.xsd">
> <!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为
> context名称空间和约束中-->
> <context:component-scan base-package="bjtu"></context:component-scan>
> </beans>
>

1. 常用的注解分类

1.1 用于创建对象的

他们的作用就和在 XML 配置文件中编写一个 标签实现的功能是一样的, 相当于:

id
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

- **@Component**

作用:用于把当前类对象存入spring容器中

属性: value:指定bean的id。如果不指定value属性,默认bean的id是当前类的类名。首字母小写。

- @Controller:一般用在表现层
- @Service:一般用在业务层
- @Repository:一般用在持久层

以上三个注解他们的作用和属性与Component是一模一样。
他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰

## 1.2 用于注入数据的

相当于:<property name="" ref=""><property name="" value="">

- **@Autowired**
- 自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他bean类型。当有多个类型匹配时,使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功。找不到就报错。

- **@Qualifier**
- 在自动按照类型注入的基础之上,再按照Bean的id注入。它在给字段注入时不能独立使用,必须和@Autowire一起使用;但是给方法参数注入时,可以独立使用。
- 属性: value:指定bean的id。

- **@Resource**
- 直接按照Bean的id注入。它也只能注入其他bean类型。
- 属性:name:指定bean的id

以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。 另外,集合类型的注入只能通过XML来实现。

- **@Value**
- 注入基本数据类型和String类型数据的
- 属性: value:用于指定值

## 1.3 用于改变作用范围的

相当于:<bean id="" class="" scope="">

- **@Scope**
- 指定bean的作用范围。
- 属性:value:指定范围的值。 取值:singleton、prototype、requestsession、 globalsession

## 1.4 和生命周期相关的

相当于:```<bean id="" class="" init-method="" destroy-method="" />

  • @PostConstruct: 用于指定初始化方法。
  • @PreDestroy: 用于指定销毁方法。

2. 新注解

  • @Configuration

    • 用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationConfigApplicationContext (有 @Configuration 注解的类.class )。
    • 当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写

      1
      public class SpringConfiguration { }
  • @ComponentScan

    • 用于指定spring在初始化容器时要扫描的包。作用和在spring的xml配置文件中的: <context:component-scan base-package="com.itheima"/> 是一样的。

    • 属性: basePackages:用于指定要扫描的包

  • @Bean

    • 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入spring容器。
    • 属性: name:给当前@Bean注解方法创建的对象指定一个名称(即bean的id)。
    • 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。 查找的方式和Autowired注解的作用是一样的。
  • @Import

    • 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration注解。当然,写上也没问题。
    • 属性: value[]:用于指定其他配置类的字节码

    • 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
     
/**
* 主配置类:bjtu/config/SpringConfiguration.java
*/
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")

public class SpringConfiguration {
}
/**
* 和spring连接数据库相关的配置类:bjtu/config/JdbcConfig.java
*/
public class JdbcConfig {

@Value("${jdbc.driver}")
private String driver;

@Value("${jdbc.url}")
private String url;

@Value("${jdbc.username}")
private String username;

@Value("${jdbc.password}")
private String password;

/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}

/**
* 创建数据源对象
* @return
*/
@Bean(name="ds2")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}

@Bean(name="ds1")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}

Spring 中的 AOP

1. AOP的概念

  • AOP:全称是Aspect Oriented Programming即:面向切面编程。

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

    • 可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术

  • AOP的作用:

    • 在程序运行期间,使用动态代理的技术不修改源码对已有方法进行增强。
  • AOP的优势

    • 减少重复代码
    • 提高开发效率
    • 维护方便

2. Spring中AOP的细节

AOP相关术语

  • Joinpoint(连接点):
    • 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
  • Pointcut(切入点):
    • 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
  • Advice(通知/增强):
    • 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
    • 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
  • Target(目标对象):
    • 代理的目标对象。
  • Weaving(织入):
    • 是指把增强应用到目标对象来创建新的代理对象的过程。 spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
  • Proxy(代理):
    • 一个类被AOP织入增强后,就产生一个结果代理类。
  • Aspect(切面):
    • 是切入点和通知(引介)的结合

Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

2.1 基于XML的AOP配置

spring中基于XML的AOP配置步骤:

1、把通知Bean也交给spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
id属性: 是给切面提供一个唯一标识
ref属性: 是指定通知类bean的Id。
4、在aop:aspect标签的内部使用对应标签来配置通知的类型
aop:before:表示配置前置通知
    method属性:用于指定Logger类中哪个方法是前置通知
    pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
    ponitcut-ref: 用于指定切入点的表达式的引用,该引用使用aop:pointcut配置
通知类型:
    * aop:before
        - 作用: 用于配置前置通知。指定增强的方法在切入点方法之前执行
        - 执行时间:切入点方法执行之前执行
    * aop:after-returning
        - 用于配置后置通知
        - 切入点方法正常执行之后。它和异常通知只能有一个执行
    * aop:after-throwing
        - 用于配置异常通知
        - 执行时间:切入点方法执行产生异常后执行。它和后置通知只能执行一个
    * aop:after
        - 用于配置最终通知
        - 执行时间:无论切入点方法执行时是否有异常,它都会在其后面执行。
5. 使用aop:pointcut配置切入点表达式
1) 作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。

2) 属性:
    id属性: 用于指定表达式的唯一标识
    expression属性: 用于指定表达式内容

此标签写在aop:aspect标签内部只能当前切面使用。 它还可以写在aop:aspect外面,此时就变成了所有切面可用

3)切入点表达式的写法:
    关键字:execution(表达式)
    表达式:
        访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表)
    * 标准的表达式写法:
        public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
    * 访问修饰符可以省略
        void com.itheima.service.impl.AccountServiceImpl.saveAccount()
    * 返回值可以使用通配符,表示任意返回值
        * com.itheima.service.impl.AccountServiceImpl.saveAccount()
    * 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
        * *.*.*.*.AccountServiceImpl.saveAccount())
    * 包名可以使用..表示当前包及其子包
        * *..AccountServiceImpl.saveAccount()
    * 类名和方法名都可以使用*来实现通配
        * *..*.*()

    * 参数列表:
        可以直接写数据类型:
            基本类型直接写名称           int
            引用类型写包名.类名的方式   java.lang.String
        可以使用通配符表示任意类型,但是必须有参数
        可以使用..表示有无参数均可,有参数可以是任意类型

    * 全通配写法:
        * *..*.*(..)

    * 实际开发中切入点表达式的通常写法:
        切到业务层实现类下的所有方法
            * com.itheima.service.impl.*.*(..)

bean.xml配置文件的配置:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
> <?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
> http://www.springframework.org/schema/beans/spring-beans.xsd
> http://www.springframework.org/schema/aop
> http://www.springframework.org/schema/aop/spring-aop.xsd">
>
> <!-- 配置srping的Ioc,把service对象配置进来-->
> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
>
> <!-- 配置Logger类: 通知类-->
> <bean id="logger" class="com.itheima.utils.Logger"></bean>
>
> <!--配置AOP-->
> <aop:config>
> <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
> 此标签写在aop:aspect标签内部只能当前切面使用。
> 它还可以写在aop:aspect外面,此时就变成了所有切面可用
> -->
> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
>
> <!--配置切面 -->
> <aop:aspect id="logAdvice" ref="logger">
> <!-- 配置前置通知:在切入点方法执行之前执行
> <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->
>
> <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
> <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
>
> <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
>
> <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
> <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
>
> <!-- 配置环绕通知 详细的注释请看Logger类中-->
> <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
> </aop:aspect>
> </aop:config>
>
> </beans>
>
6. 环绕通知
aop:around: 
    作用: 用于配置环绕通知
        属性: method:指定通知中方法的名称。 
        pointct:定义切入点表达式 
        pointcut-ref:指定切入点表达式的引用
  • 环绕通知是spring框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
  • 通常情况下,环绕通知都是独立使用的
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
 
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
public class Logger {

/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}

/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}

/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}

/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数

System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}

2.2 基于注解的AOP配置