一 Spring的概要
1.1 简介
Spring,春天的意思,意指给软件行业带来春天。2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
官网 : Spring | Home
官方下载地址 : JFrog
GitHub : Spring · GitHub
1.2 优点
-
Spring是一个开源的轻量级的应用开发框架,其目的是 用于简化企业级应用程序开发,降低侵入性;
-
Spring提供的IOC和AOP功能,可以将组建的耦合度降至最低,即解耦,便于系统日后的维护和升级
-
提供了对持久层、事务的支持
-
提供了MVC框架思想的实现,用于开发企业级应用。
-
也可以与第三方框架和技术整合应用,可以自由选择采用哪种技术进行开发
一句话概括:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
1.3 Spring的发展历程
1997 年 IBM提出了EJB 的思想
1998 年,SUN制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布
Rod Johnson (罗德·约翰逊,Spring 之父)
Expert One-to-One J2EE Development without EJB(2004)
阐述了 J2EE 开发不使用 EJB的解决方式(Spring 雏形)
2017年9月份发布了Spring的最新版本Spring 5.0通用版
1.4 模块
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式。
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
-
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
-
Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
-
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
-
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
-
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
-
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
-
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
1.5 拓展 Spring Boot与Spring Cloud
-
Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;
-
Spring Cloud是基于Spring Boot实现的;
-
Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
-
Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
-
SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。
二 Spring IOC
2.1 IOC简介
IoC(Inverse Of Control)控制反转:即,把创建对象的权利交给框架,也就是指将对象的创建、对象的初始化、对象的存储、对象的管理交给了Spring容器。IOC本质是一种设计思想。
IoC 是一种通过描述来生成或者获取对象的技术,对于Java初学者 更多时候所熟悉的是使用 new 关键字来创建对象,而在spring中则不是,它是通过描述(XML或注解)来创建对象。
在 Spring 中把每一个需要管理的对象称之为 Spring Bean(简称为Bean),而Spring中管理这些 Bean 的容器,被我们称之为 Spring IoC 容器(简称 IoC容器)
在此之前,当需要对象时,通常是利用new关键字创建一个对象:
/* 创建一个User对象——这里使用new对象的方式造成了程序之间的耦合性提升 */ User u = new User();
但由于new对象,会提高类和类之间的依赖关系,即代码之间的耦合性。
而现在我们可以将对象的创建交给框架来做:
// 获取Spring IoC容器对象 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); // 从spring的IoC容器中获取User对象 private User user = (User)ctx.getBean("user");
将需要的POJO提前在配置文件(XML或注解)中进行描述,Spring会根据配置文件将其中所描述生成 IoC容器并装配Bean,在需要使用时,再次获取即可
这样一来,当需要对象时不用自己创建,而是通过框架获取即可,不用硬编码去new对象,自然就降低类和类之间的依赖关系,也就是耦合性。
2.2 入门案例
在Spring中允许我们通过XML或注解方式装配Bean,下面先来介绍Spring中通过XML方法如何装配Bean。
扩展:
idea里的project 相当于 eclipse里的 workspace
idea里的module 相当于eclipse里的project 为了迎合现在的模块形式开发。
module可以称之为模块或者是子项目,依赖于父项目(project) . 开发时一般都是开发的module,因此父项目的src可以删掉
隐藏不想看到的文件或者文件夹
File—>settings—>Editor—>File Types—>ignored Files and Folders —->点击+号, 输入想要隐藏的内容,回车
2.2.1 创建module
先创建父工程spring-demo,然后再创建module,命名spring-ioc1
<dependencies> <!-- 添加commons-logging依赖 --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <!-- 添加junit依赖 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> </dependency> <!-- 添加spring的依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version> </dependency> </dependencies>
spring-core:Spring的核心工具包
这个jar 文件包含Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。 外部依赖Commons-Logging, Log4J。
spring-beans:Spring IOC的基础实现,包含访问配置文件、创建和管理bean等。
这个jar文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean 以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI 支持,引入spring-core.jar 及spring-beans.jar 文件就可以了。 外部依赖spring-core,cglib-nodep.jar。
spring-context:在基础IOC功能上提供扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度、JNDI定位,EJB集成、远程访问、缓存以及多种视图层框架的支持。
这个jar文件为Spring核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,JDNI 所需的全部类,instrumentation组件以及校验Validation 方面的相关类。
外部依赖spring-core.jar,spring-beans.jar,spring-aop.jar,commons-collections.jar,aopalliance.jar。
spring-expression:Spring表达式语言
spring-aop:Spring的面向切面编程,提供AOP(面向切面编程)的实现
这个jar文件包含在应用中使用Spring的AOP特性时所需的类。使用基于AOP的Spring特性,如声明型事务管理(Declarative Transaction Management),也要在应用里包含这个jar包。
外部依赖spring-core.jar,spring-beans.jar,cglib-nodep.jar,aopalliance.jar
附:aopalliance.jar:这个包是AOP联盟的API包,里面包含了针对面向切面的接口。通常Spring等其它具备动态织入功能的框架依赖此包。
cglib-nodep.jar:这个包是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。 当然这些实际的功能是asm所提供的,asm又是什么?Java字节码操控框架。cglib就是封装了asm,简化了asm的操作,实现了在运行期动态生成新的class。 实际上CGlib为spring aop提供了底层的一种实现;为hibernate使用cglib动态生成VO/PO (接口层对象)。
2.2.2 编写实体类
package com.sldl.pojo; public class User { private String username; public User() { } public User(String username) { this.username = username; } public void setUsername(String username) { this.username = username; } @Override public String toString() { return "User{" + "username='" + username + '\'' + '}'; } }
2.2.3 编写核心配置文件
在src/main/resources目录下 ,创建Spring的核心配置文件, 这里我们命名为beans.xml (其实,文件名可以随意)
<?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"> </beans>
2.2.4 配置Bean
在 beans.xml 文件中添加如下配置:将User类作为Bean装配到Spring IoC容器中
<?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"> <!--在spring的配置文件里,注册User类的bean配置, 此时User类的对象就是Spring容器帮助我们创建对象,默认调用无参构造器--> <bean id="u1" class="com.sldl.pojo.User"></bean> </beans>
2.3 Bean的作用域
在Spring容器中管理的Bean对象的作用域,可以通过scope属性或用相关注解指定其作用范围。
<bean id="user" scope="singleton" class="xxxxx.User"></bean>
2.3.1 作用域的种类
1)singleton
单实例(默认),这个作用域标识的对象具备全局唯一性。
-
当把一个 bean 定义设置 scope 为singleton作用域时,那么Spring IoC容器只会创建该bean定义的唯一实例。也就是说,整个Spring IoC容器中只会创建当前类的唯一一个对象。
-
这个单一实例会被存储到 bean池中,并且所有针对该 bean 的后续请求和引用都将返回被缓存的、唯一的这个对象实例。
-
单例的bean对象由spring负责创建、初始化、存储、销毁(在spring容器销毁时销毁)
配置如下
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
测试:
@Test public void test03(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); User user2 = (User) context.getBean("user"); System.out.println(user==user2); }
2)prototype
多实例。这个作用域标识的对象每次获取都会创建新的对象。
当把一个 bean 定义设置 scope 为prototype作用域时,Spring IoC容器会在每一次获取当前Bean时,都会产生一个新的Bean实例(相当于new的操作),该实例不会被存储到bean池中,spring也不负责销毁,当程序不再使用这个对象时,由GC负责销毁。
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 或者 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
3)Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
4)Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
2.3.2 作用域的选择
咱们从使用频率和线程安全的角度来思考
1)从使用频次上考虑,如果一个对象使用的频率非常高,建议使用单例,这样会将bean对象存储到bean池中,从始至终只有一个实例,可以减少对象创建,减少对资源的消耗。
2)在没有线程安全问题的前提下,没必要每次获取都创建一个对象,这样子既浪费CPU又浪费内存;
3)从使用频次上考虑,如果一个对象使用的频率非常低,没有必要将对象存储到map中(存储到bean池中的对象会一直存在bean池中,在spring容器销毁时销毁),建议使用多例。
4)如果一个对象被多个线程所共享,并且对象中存在共享数据,一旦这个数据被多个线程所操作,就可能会产生线程不安全问题,可以考虑使用多例
2.3.3 Bean的生命周期
生命周期:指一个对象何时创建、何时销毁以及从创建之后到销毁之前的所处的状态
单实例对象(singleton):
出生:当spring容器对象创建时,bean对象就会被创建 活着:只要容器没有销毁,bean对象就会一直存活 死亡:当spring容器销毁,bean对象也会跟着消亡 总结:单例对象的生命周期和容器相同,spring容器负责singleton对象的创建、存储、销毁(随着spring容器销毁而销毁)。
多实例对象(prototype):
出生:当获取bean对象时,spring框架才会为我们创建bean对象 活着:只要对象是在使用过程中,就会一直存活 死亡:当对象长时间不用,且没有别的对象引用时,由Java垃圾回收机制负责回收 总结:spring容器只负责prototype对象的创建和初始化,不负责存储和销毁。当对象长时间不用时,由Java垃圾回收机制负责回收
2.4 依赖注入(DI)
DI(Dependency Injection)依赖注入 。即组件之间的依赖关系由容器在应用系统(项目)运行期来决定,也就是由容器动态地将存在某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。
简单来说,所谓的依赖注入其实就是,在创建对象的同时或之后,如何给对象的属性赋值。
如果对象由我们自己创建,这一切都变得很简单,例如:
User user = new User(); user.setName("小明"); //通过setName方法为name属性赋值 user.setAge(18); //通过setAge方法为age属性赋值
或者:
User user = new User("小明", 18); //在创建对象的同时,通过有参构造函数为对象的属性赋值
如果对象由Spring IoC容器创建,那么Spring是怎么给属性赋值的?Spring主要提供两种方式为属性赋值:
即:set方法注入和构造方法注入。
此外还有:@Autowired注解方式;静态工厂的方法注入;实例工厂的方法注入等。
2.4.1 set方法注入
顾名思义,即要求被注入的属性 , 必须在类中有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is。spring框架底层会调用set方法为成员变量赋值。
例如:Spring框架负责创建User的Bean对象之后,会通过调用User对象的setName方法为name属性赋值。
1)数据准备
Address.java
package com.shuilidianli.pojo; public class Address { private String province; private String city; public void setProvince(String province) { this.province = province; } public void setCity(String city) { this.city = city; } @Override public String toString() { return "Address{" + "province='" + province + '\'' + ", city='" + city + '\'' + '}'; } }
Student.java
package com.shuilidianli.pojo; import java.util.*; public class Student { private int id; private String name; private Address addr; private String[] books; private List<String> hobbys; private Set<String> games; private Map<String,String> cards; private Properties info; private String mgr; public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setAddr(Address addr) { this.addr = addr; } public void setBooks(String[] books) { this.books = books; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public void setGames(Set<String> games) { this.games = games; } public void setCards(Map<String, String> cards) { this.cards = cards; } public void setInfo(Properties info) { this.info = info; } public void setMgr(String mgr) { this.mgr = mgr; } @Override public String toString() { return "Employee{" + "id=" + id + "\n"+ ", name='" + name + '\'' +"\n"+ ", addr=" + addr +"\n"+ ", books=" + Arrays.toString(books) +"\n"+ ", hobbys=" + hobbys +"\n"+ ", games=" + games +"\n"+ ", cards=" + cards +"\n"+ ", info=" + info +"\n"+ ", mgr=" + mgr + '}'; } }
2)常量注入
<bean id="e1" class="com.shuilidianli.pojo.Employee"> <property name="id" value="1001"/> <property name="name" value="小明"/> </bean>
测试:
package com.shuilidianli.test; import com.shuilidianli.pojo.Employee; import com.shuilidianli.pojo.User; import com.shuilidianli.web.EmpController; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class IOCTest { @Test public void test1() { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); Employee con = ctx.getBean("emp1", Employee.class); System.out.println(con); } }
3)Bean注入
注意:这里的值是一个引用,ref
<bean id="a1" class="com.shuilidianli.pojo.Address"> <property name="province" value="吉林"/> <property name="city" value="长春"/> </bean> <bean id="e1" class="com.shuilidianli.pojo.Employee"> <property name="id" value="1001"/> <property name="name" value="小明"/> <property name="addr" ref="a1"/> </bean>
4)数组注入
<bean id="e1" class="com.shuilidianli.pojo.Employee"> <property name="id" value="1001"/> <property name="name" value="小明"/> <property name="addr" ref="a1"/> <property name="books"> <array> <value>完美世界</value> <value>无限恐怖</value> <value>警世通言</value> </array> </property> </bean>
5)List注入
<property name="hobbys"> <list> <value>读书</value> <value>电影</value> <value>音乐</value> </list> </property>
6)set注入
<property name="games"> <set> <value>王者荣耀</value> <value>和平精英</value> <value>原神</value> </set> </property>
7)Map注入
<property name="cards"> <map> <entry key="中国邮政" value="100100001111"/> <entry key="中国建设" value="200200002222"/> </map> </property>
8)Properties注入
<property name="info"> <props> <prop key="A001">迈巴赫</prop> <prop key="A002">玛莎拉蒂</prop> <prop key="A003">劳斯莱斯</prop> </props> </property>
9)Null注入
<property name="mgr"> <null/> </property>
10)p命名空间
什么是 P 命名空间?
P命名空间 不是新的东西,它是对 IoC 和 DI 的简化。使用 p 命名空间 可以更加方便地完成 bean 的配置以及 bean 之间的依赖注入。
如何使用 p 命名空间?
首先必须确保 xml 文件中引入了 p 命名空间。
..... xmlns:p="http://www.springframework.org/schema/p" .....
修改bean
<bean id="a1" class="com.shuilidianli.pojo.Address"> <property name="province" value="吉林"/> <property name="city" value="长春"/> </bean> <!--P(属性: properties)命名空间 , 属性依然要设置set方法--> <bean id="e1" class="com.shuilidianli.pojo.Employee" p:id="1002" p:name="小红" p:addr-ref="a1"> <property name="books"> <array> <value>完美世界</value> <value>无限恐怖</value> <value>警世通言</value> </array> </property> //....... </bean>
顾名思义,就是使用类中的构造函数,为成员变量赋值——即构造方法注入。
需要注意的是:为成员变量赋值是通过配置的方式,让spring框架在创建bean对象的同时,为成员变量赋值。而不是我们自己去实现。
1)准备数据
package com.shuilidianli.pojo; public class Student { private int id; private String name; private int age; private Address addr; //提供无参构造器 public Student() { } //提供有参数构造器 注:如果添加了有参构造器,无参构造器必须添加。 因为框架底层很多地方都用到了无参构造器的 public Student(int id, String name, int age,Address addr) { this.id = id; this.name = name; this.age = age; this.addr = addr; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setAddr(Address addr) { this.addr = addr; } @Override public String toString() { return "Student{" + "id=" + id + "\n" + ", name='" + name + '\'' +"\n" + ", age=" + age +"\n" + ", addr=" + addr + '}'; } }
2)配置beans.xml文件
<!-- 将Student类作为bean装配到Spring IoC容器中(即Student类的实例由Spring创建) --> <bean id="student" class="com.shuilidianli.pojo.Student"> <!-- 通过构造方法为Student Bean的id、name、age、addr属性赋值 --> <constructor-arg name="id" value="2001"/> <constructor-arg name="name" value="小天"/> <constructor-arg name="age" value="23"/> <constructor-arg name="addr" ref="a1"/> </bean>
其中,constructor-arg标签name属性的值必须和构造函数中参数的名字相同!
同样的,普通属性直接通过value注入即可;对象属性通过ref属性注入。
2.5 自动装配
-
自动装配是使用spring满足bean依赖的一种方法
-
spring会在应用上下文中为某个bean寻找其依赖的bean。
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
-
组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
-
自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显式的配置降低到最少。
2.5.1 数据准备
新建项目
新建两个实体类,Cat Dog 都有一个叫的方法
public class Cat { public void call() { System.out.println("miao~miao~miao"); } } public class Dog { public void call() { System.out.println("wang~wang~wang"); } }
User.java
package com.sldl.pojo; public class User { private String name; private Dog dog; private Cat cat; public void setName(String name) { this.name = name; } public void setDog(Dog dog) { this.dog = dog; } public void setCat(Cat cat) { this.cat = cat; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", dog=" + dog + ", cat=" + cat + '}'; } }
配置文件
<?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 id="dog" class="com.sldl.pojo.Dog"></bean> <bean id="cat" class="com.sldl.pojo.Cat"></bean> <bean id="user" class="com.sldl.pojo.User"> <property name="name" value="小明"/> <property name="dog" ref="dog"/> <property name="cat" ref="cat"/> </bean> </beans>
测试没问题,环境OK
public class IOCTest { ApplicationContext ctx = null; @Before public void testBefore(){ ctx = new ClassPathXmlApplicationContext("beans.xml"); } @Test public void test1(){ //获取UserBean User user = ctx.getBean("user", User.class); System.out.println(user); } }
2.5.2 byName
autowire byName (按名称自动装配)。由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。
1)修改bean.xml配置
增加一个属性 autowire=“byName”
<?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 id="dog" class="com.sldl.pojo.Dog"></bean> <bean id="cat" class="com.sldl.pojo.Cat"></bean> <!-- User的属性叫cat和dog ByName自动装备是通过相同的名字进行匹配--> <bean id="user" class="com.sldl.pojo.User" autowire="byName"> <property name="name" value="小明"/> <!--<property name="dog" ref="dog"/> <property name="cat" ref="cat"/>--> </bean> </beans>
2)修改dog和cat的bean的id值
如下
<bean id="dogX" class="com.sldl.pojo.Dog"></bean> <bean id="catX" class="com.sldl.pojo.Cat"></bean>
将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
去spring容器中寻找是否有此字符串名称id的对象。
如果有,就取出注入;如果没有,就是null,没赋值上
2.5.3 byType
autowire=”byType” (按类型自动装配)。使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。NoUniqueBeanDefinitionException 1
1)将user的bean配置修改一下 : autowire=“byType”
2)测试,正常输出
<bean id="dog" class="com.sldl.pojo.Dog"></bean> <bean id="cat" class="com.sldl.pojo.Cat"></bean> <bean id="cat2" class="com.sldl.pojo.Cat"></bean> <bean id="user" class="com.sldl.pojo.User" autowire="byType"> <property name="str" value="小明"/> </bean>
正常有警告如下:
Could not autowire. There is more than one bean of 'Cat' type. Beans: cat,cat2. Properties: 'cat'
4)测试,报错:NoUniqueBeanDefinitionException
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.sldl.pojo.Cat' available: expected single matching bean but found 2: cat,cat2
表示,在容器中,找到了多个该类型的Bean,不是唯一的,因此赋值失败。
5)删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
这就是按照类型自动装配!
小结:
-
byName的时候,需要保证所有的bean的id唯一,并且这个bean需要和自动注入的属性set方法的值一致!
-
byType的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
2.6 使用注解
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作:利用注解的方式注入属性。
1、在spring配置文件中引入context文件头
<?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"> <--- </beans>
用的时候,别忘记删掉 箭头 O(∩_∩)O哈哈~
2、开启属性注解支持!
<context:annotation-config/>
其实就是代替下面四个类型的bean注入
com.AutowiredAnnotationBeanPostProcessor CommonAnnotationBeanPostProcessor PersistenceAnnotationBeanPostProcessor RequiredAnnotationBeanPostProcessor
2.6.1 @Autowired
@Autowired是按类型自动注入的,不支持id匹配。 确认一下,是否引入 spring-aop的包!
测试:
1、将User类中的set方法去掉,使用@Autowired注解
package com.sldl.pojo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; public class User { private String name; @Autowired private Dog dog; @Autowired private Cat cat; public void setName(String name) { this.name = name; } /* public void setDog(Dog dog) { this.dog = dog; } public void setCat(Cat cat) { this.cat = cat; }*/ @Override public String toString() { return "User{" + "name='" + name + '\'' + ", dog=" + dog + ", cat=" + cat + '}'; } }
2、此时配置文件内容
<context:annotation-config/> <bean id="dog" class="com.sldl.pojo.Dog"></bean> <bean id="cat" class="com.sldl.pojo.Cat"></bean> <bean id="user" class="com.sldl.pojo.User"> <property name="name" value="小明"/> <!-- <property name="dog" ref="dog"/> <property name="cat" ref="cat"/> --> </bean>
3、小结:
@Autowired(required=false) 说明:false,对象可以为null; true,不能为null。
//如果允许对象为null,设置required = false,默认为true @Autowired(required = false) private Cat cat;
2.6.2 @Qualifier
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
@Qualifier不能单独使用。
测试实验步骤:
1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
<bean id="dog1" class="com.sldl.pojo.Dog"></bean> <bean id="dog2" class="com.sldl.pojo.Dog"></bean> <bean id="cat1" class="com.sldl.pojo.Cat"></bean> <bean id="cat2" class="com.sldl.pojo.Cat"></bean>
2、没有加Qualifier测试,直接报错。因为类型不唯一。
3、在属性上添加Qualifier注解
public class User { private String name; @Autowired @Qualifier("dog2") private Dog dog; @Autowired @Qualifier("cat2") private Cat cat; //....... }
2.6.3 @Resource
@Resource如有指定的name属性,先按该属性进行byName方式查找装配;
-
其次再进行默认的byName方式进行装配;
-
如果以上都不成功,则按byType的方式自动装配。
-
都不成功,则报异常。
1)测试1
实体类:
public class User { private String name; //如果允许对象为null,设置required = false,默认为true @Resource(name = "cat2") private Cat cat; @Resource private Dog dog; //..... }
beans.xml
<bean id="dog" class="com.sldl.pojo.Dog"/> <bean id="cat1" class="com.sldl.pojo.Cat"/> <bean id="cat2" class="com.sldl.pojo.Cat"/> <bean id="user" class="com.sldl.pojo.User"> <property name="name" value="小明"/> <bean>
测试:结果OK
2)测试2
配置文件:beans.xml , 删掉cat2
<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/>
实体类上只保留注解
@Resource private Cat cat; @Resource private Dog dog;
结果:OK
结论:先进行byName查找,失败;再进行byType查找,成功。
3)小结
@Autowired与@Resource异同:
-
@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
-
@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖的bean在容器中存在,否则报错。如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
-
@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
-
它们的作用相同,都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,再byName,
@Resource先byName,再byType
2.6.4 @Component
1)说明
在spring4之后,想要使用注解形式,必须得要引入aop的包,检查自己是否引入了aop依赖包
在配置文件当中,还得要引入一个context约束
<?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"> </beans>
2)Bean的实现
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
步骤1 配置扫描哪些包下的注解
<!--指定注解扫描包--> <context:component-scan base-package="com.kuang.pojo"/>
步骤2 在指定包下编写类,增加注解
@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { public String name = "小明"; }
步骤3 测试
@Test public void test(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) applicationContext.getBean("user"); System.out.println(user.name); }
3)属性注入
使用注解注入属性
1、可以不用提供set方法,直接在属性名上添加@value(“值”)
@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { @Value("小明") // 相当于配置文件中 <property name="name" value="小明"/> public String name; }
2、如果提供了set方法,最好在set方法上添加@value(“值”);
@Component("user") public class User { public String name; @Value("小明") public void setName(String name) { this.name = name; } }
4) 衍生注解
这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
-
@Controller <====web层
-
@Service <====service层
-
@Repository <==== dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
2.6.5 作用域@scope
singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user") @Scope("prototype") public class User { @Value("小明") public String name; }
2.7 小结
XML与注解比较
-
XML可以适用任何场景 ,结构清晰,维护方便
-
注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发 :推荐最佳实践
-
xml管理Bean
-
注解完成属性注入
-
使用过程中, 可以不用扫描,扫描是为了类上的注解
针对于下面的配置,再次说明一下:
<context:annotation-config/> @Autowired @Quilfi... @Resource
作用:
-
进行注解驱动注册,从而使注解生效
-
用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显式的向Spring注册
-
如果不扫描包,就需要手动配置bean
-
如果不加注解驱动,则注入的值为null!
-
如果开启了包扫描, 就不用再添加 注册驱动开关了
<context:component-scan base-package="com.springioc.pojo"/> 代替了 <context:annotation-config/>
2.8 基于Java类进行配置
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
2.8.1 测试1
1、编写一个实体类,Phone
package com.sldl.pojo; public class Phone { private String brand; private String model; private double price; public Phone(String brand, String model, double price) { this.brand = brand; this.model = model; this.price = price; } @Override public String toString() { return "Phone{" + "brand='" + brand + '\'' + ", model='" + model + '\'' + ", price=" + price + '}'; } }
2、新建一个config配置包,编写一个MyConfig配置类
package com.sldl.config; import com.sldl.pojo.Phone; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //该注解的作用是将所在类,注册成一个类似于beans.xml的配置文件 @Configuration public class MyConfig { @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id! public Phone phone(){ return new Phone("MI","14Pro",6800); } }
3、测试
@Test public void test2(){ ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class); Phone phone = app.getBean("phone", Phone.class); System.out.println(phone); }
2.8.2 测试2
如何导入其他配置呢?
1、再弄一个pojo类
package com.sldl.pojo; public class Computer { private String brand; private String model; private double price; public Computer(String brand, String model, double price) { this.brand = brand; this.model = model; this.price = price; } @Override public String toString() { return "Computer{" + "brand='" + brand + '\'' + ", model='" + model + '\'' + ", price=" + price + '}'; } }
2、我们再编写一个配置类!
package com.sldl.config; import com.sldl.pojo.Computer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //该注解的作用是将所在类,注册成一个类似于beans.xml的配置文件 @Configuration public class MyConfig2 { @Bean public Computer computer(){ return new Computer("thinkPad","X1",10000); } }
3、引入新的配置类
在之前的配置类中我们来选择导入这个配置类
package com.sldl.config; import com.sldl.pojo.Phone; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; //该注解的作用是将所在类,注册成一个类似于beans.xml的配置文件 @Configuration @Import(MyConfig2.class) public class MyConfig { @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id! public Phone phone(){ return new Phone("MI","14Pro",6800); } }
4、测试
@Test public void test2(){ ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class); Phone phone = app.getBean("phone", Phone.class); System.out.println(phone); Computer com = app.getBean("computer", Computer.class); System.out.println(com); }
关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!
三 SpringAOP
在学习编程过程中,我们对于公共方法的处理应该是这样的一个过程,初期阶段如下
f1(){ Date now = new Date(); System.out.println("功能执行之前的各种前置工作"+now) //...功能代码 //...功能代码 System.out.println("功能执行之前的各种后置工作") } f2(){ System.out.println("功能执行之前的各种前置工作") //...功能代码 //...功能代码 System.out.println("功能执行之前的各种后置工作") } .....
然后中期阶段,我们学会了封装
public class AspectConfig{ static void before(){ System.out.println("功能执行之前的各种前置工作") } static void after(){ System.out.println("功能执行之前的各种后置工作") } }
AspectConfig.before() f1() AspectConfig.after() AspectConfig.before()---切面-- 块 -份--模块 f2() AspectConfig.after()
现在,我们可以采用另外一种更简单的方式,AOP思想,对代码进行无侵入式实现,这样更高级了。
public class AspectConfig{ @express(*f*) static void before(){ System.out.println("功能执行之前的各种前置工作") } //@expres(*f*) static void after(){ System.out.println("功能执行之前的各种后置工作") } }
m(){ f1() } n(){ f2() }
3.1 AOP思想概述
3.1.1 思想简介
AOP(Aspect Oriented Programming)意为:面向切面编程,它是一种思想,是对某⼀类事情的集中处理。
它的核心思想是 将方法在执行过程中切分为多个部分,也就是多个横切关注点,这样可以与业务逻辑代码分离开来,使代码模块化,进而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP是OOP(面向对象编程)的一个补充,它允许开发者以声明方式实现关注点,而不是通过在业务逻辑代码中散布大量重复代码。
AOP 可以通过多种方式实现,包括:
-
编译时增强:在编译期间通过修改字节码来实现AOP。
-
类加载时增强:在类加载到JVM时通过字节码操作实现AOP。
-
动态代理:在程序运行时,通过代理对象来实现AOP。
3.1.3 AOP的应用场景
1)日志记录
2)声明式事务管理
3)登录安全验证
4)统一异常处理
3.1.3 AOP中的核心概念
1)横切关注点
在程序中,可以跨越多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 … 简单来说,就是程序员要关注的事情,抽象概念
2)切面(Aspect)
横切关注点被模块化的一个体现,它是一个类。这个类由通知(Advice)和切点(Pointcut)组成,既包含了横切逻辑的定义,也包含了连接点的定义。
3)通知(Advice)
通知,实际上是一个拦截器,它定义了切面是做什么以及何时使用,即在某个特定的连接点上执行的动作,它是切面的具体实现
以目标方法为参照点,根据放置位置的地方不同,通知分为如下5种类型通知:
-
前置通知(Before):在方法执行前执行。
-
后置通知(After):在方法执行后执行。
-
返回通知(After Returning):在方法成功返回后执行。
-
异常通知(After Throwing):在方法抛出异常后执行。
-
环绕通知(Around):包围方法执行的前后。
4)切入点(PointCut)
Advice 执行的 “地点”的定义。 简单理解:就是用来定义位置
5)连接点(JointPoint)
与切入点匹配的执行点。这个点可以是方法调用时,抛出异常时,甚至修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。 简单理解:就是具体切入的位置
6)目标(Target)
就是被切面作用的对象,包含连接点的对象
3.2 Spring AOP简介
AOP是一个思想,是一个编程范式。SpringAOP是对这个思想的实现。
Spring AOP 是构建在动态代理基础上的。因此 Spring 对 AOP 的⽀持局限于⽅法级别的拦截。 Spring AOP ⽀持 JDK Proxy
和 CGLIB
⽅式实现动态代理。默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。
织⼊(Weaving):代理的⽣成时机
织入就是什么时候把代理的代码放进运行的代码中。在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:
编译期(编译阶段):
切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就 是以这种⽅式织⼊切⾯的。
类加载期:
切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器 (ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载 时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊切⾯。
运⾏期:
切⾯在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP容器会为⽬标对象动态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊切⾯的。
3.3 XML配置方式
3.3.1 导入依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
3.3.2 案例1
UserService.java
public interface UserService { void add(); void delete(); void update(); void search(); }
UserServiceImpl.java
public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("---增加用户---"); } @Override public void delete() { System.out.println("---删除用户---"); } @Override public void update() { System.out.println("---修改用户---"); } @Override public void search() { System.out.println("---查询用户---"); } }
编写两个通知类型,一个前置通知,一个后置通知
import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class Log implements MethodBeforeAdvice { /** * * @param method 目标对象的方法 * @param objects 被调用的方法的参数 * @param o 目标对象 * @throws Throwable */ @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println(o.getClass().getName()+"的"+method.getName()+"正在执行····"); } }
import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; public class AfterLog implements AfterReturningAdvice { /** * * @param o 方法的返回值 * @param method 目标对象的方法 * @param objects 目标对象的方法的参数 * @param o1 目标对象 * @throws Throwable */ @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("执行了"+o1.getClass().getName()+"的"+method.getName()+"方法," + "返回值:"+o); } }
注册bean,实现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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册--> <bean id="userService" class="com.shuilidianli.service.UserServiceImpl"></bean> <bean id="log" class="com.shuilidianli.aop.Log"></bean> <bean id="afterLog" class="com.shuilidianli.aop.AfterLog"></bean> <!--aop配置--> <aop:config> <!--切入点 expression: 表达式匹配要执行的方法--> <aop:pointcut id="pointcut" expression="execution(* com.shuilidianli.service.UserServiceImpl.*(..))"/> <!--执行环绕:advice-ref执行方法,pointcut-ref:切入点--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>
测试
import com.shuilidianli.service.UserService; import com.shuilidianli.web.EmpController; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AopTest2 { @Test public void test1(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); UserService us = (UserService) ctx.getBean("userService"); us.search(); } }
3.3.2 案例2
自定义类来实现Aop
目标业务类不变依旧是userServiceImpl
1)自己定义一个切入类
package com.shuilidianli.aop; public class DiyPointcut { public void before(){ System.out.println("---------方法执行前---------"); } public void after(){ System.out.println("---------方法执行后---------"); } }
2)注册配置
<!--第二种方式自定义实现--> <!--注册bean--> <bean id="diy" class="com.shuilidianli.aop.DiyPointcut"></bean> <!--aop的配置--> <aop:config> <!--第二种方式:使用AOP的标签实现--> <aop:aspect ref="diy"> <aop:pointcut id="diyPointcut" expression="execution(* com.shuilidianli.service.UserServiceImpl.*(..))"/> <aop:before method="before" pointcut-ref="diyPointcut"/> <aop:after method="after" pointcut-ref="diyPointcut"/> </aop:aspect> </aop:config>
3)测试
package com.shuilidianli.test; import com.shuilidianli.service.UserService; import com.shuilidianli.web.EmpController; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AopTest2 { @Test public void test1(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); UserService us = (UserService) ctx.getBean("userService"); us.search(); } }
<context:component-scan base-package="com"/> <!-- 开启aop组件注解扫描 --> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
aop:aspectj-autoproxy:说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspect切面的bean创建代理,织入切面。 当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了 <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强, 当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。 不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
3.4.2 切入点表达式语法
1)语法如下:
"execution(修饰词 返回值类型 类全限定名.方法名(形参列表) 异常)" public int f1(int a,int b)throw Exception{ }
2)Spring AOP的切入点表达式非常灵活,支持模糊配置。
eg1 : execution(* 全类名.*(..)) 第一个 "*" 表示支持任意修饰符及返回值类型;第二个 "*" 表示支持该类中的任意方法;形参列表中的".."则表示可以匹配任意数量和类型的参数。(PS : 若目标类、接口与当前切面类在同一个包下,可以省略包名,只写类名) eg2 : execution(public * 全类名.*(..)) 表示支持该类中的所有公有的方法 eg3 : execution(public double 全类名.*(..)) 表示支持该类中所有公有的且返回值为double的方法 eg4 : execution(public double 全类名.*(double, ..)) 表示支持该类中所有形参列表第一个参数为double类型,且后续参数可以是任意数量任意类型的,公有的返回值为double的方法。 eg5 : execution(double 全类名.*(double, double) 表示支持该类中所有形参列表为两个double类型,公有的且返回值为double类型的方法。
3) 在AspectJ(另一个框架)中,切入点表达式可以通过"&&","||","!"等操作符结合起来。
eg : execution(* *.add(int, ..)) || execution(* *.subtract(int, ..))——表示支持任意类中的任意访问修饰符和任意返回值类型的,且形参列表第一个参数为int类型的add 或 subtract方法。
4)注意事项:
(1) 当切入点表达式直接指向了接口某个实现类的方法(非实现类特有方法),这时切入点表达式仅会对该实现类生效(动态代理 + 反射),即接口的其他实现类不会生效(不会得到代理对象,即使你以接口类型作为接收)。 (2) 当切入点表达式指向接口的方法时,切入表达式会对该接口的所有实现类生效。 (3) 切入点表达式也可以切入到没有实现接口的类的横切关注点中。(CGlib动态代理模式) PS : JDK Proxy动态代理和CGlib动态代理的区别 - JDK动态代理是面向接口的,只能增强实现类中重写了接口中的方法。而CGlib是面向父类的,可以增强父类的所有方法。 - JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类。
3.4.3 案例3
AnnotationPointcut.java
package com.shuilidianli.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect public class AnnotationPointcut { @Before("execution(* com.shuilidianli.service.UserServiceImpl.*(..))") public void before(){ System.out.println("---------方法执行前---------"); } @After("execution(* com.shuilidianli.service.UserServiceImpl.*(..))") public void after(){ System.out.println("---------方法执行后---------"); } @Around("execution(* com.shuilidianli.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("---环绕前---"); System.out.println("调用的方法:"+joinPoint.getSignature()); //执行目标方法 Object proceed = joinPoint.proceed(); System.out.println("---环绕后---"); System.out.println(proceed); } }
测试: 注意给UserServiceImpl添加注解
package com.shuilidianli.test; import com.shuilidianli.service.UserService; import com.shuilidianli.web.EmpController; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AopTest2 { @Test public void test1(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); UserService us = (UserService) ctx.getBean("userServiceImpl"); us.search(); } }
3.4.4 案例4
1)EmpController.java
package com.sldl.controller; import org.springframework.stereotype.Controller; @Controller //添加Bean注解 public class EmpController { public void findAll(){ System.out.println("---正在查询所有员工信息---"); } public void addEmp(){ System.out.println("---正在添加一个员工信息---"); String str = null; System.out.println(str.length()); } }
2)Operation.java
package com.sldl.log; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Component // 添加Bean注解 @Aspect // 添加Aop注解 public class OperationLog { //后置通知注解 @After("within(com.sldl.controller..*)") public void log(){ System.out.println("记录日志"); } //环绕通知注解 @Around("within(com.sldl.controller..*)") public Object log1(ProceedingJoinPoint p) throws Throwable{ //获取目标组件的名字 String className = p.getTarget().getClass().getName(); //获取目标组件里执行的方法名 String methodName = p.getSignature().getName(); System.out.println("------------"); //执行目标组件 Object obj = p.proceed(); System.out.println( "xxx正在执行"+className +"里的"+methodName+"方法"); return obj; } //异常抛出通知 @AfterThrowing(pointcut="within(com.sldl.controller..*)",throwing="e") public void log2(Exception e){ System.out.println(e.toString()); StackTraceElement[] eles = e.getStackTrace(); System.out.println(eles[0]); System.out.println(eles[1]); } }
3)beans.xml
<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启注解扫描功能--> <context:component-scan base-package="com.sldl"/> <!--开启AOP注解扫描--> <aop:aspectj-autoproxy proxy-target-class="true"/> </beans>
4)AOPTest
package com.sldl.test; import com.sldl.controller.EmpController; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AOPTest { @Test public void test1(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); EmpController ec = ctx.getBean("empController", EmpController.class); //ec.findAll(); ec.addEmp(); } }
测试结果:
3.4.5多个通知执行顺序
5个通知都有的情况下,执行顺序:
1.一定先执行是环绕通知的前一部分
2.再执行前置通知
3.目标方法
4.环绕通知的后一部分
5.在执行后置通知
6.再执行返回通知
7.最后执行异常通知