Spring
1. 概述
1.1 简介
①2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架
②2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版
③很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学
④Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
1.2 优点
①Spring是一个开源免费的框架 , 容器
②Spring是一个轻量级的框架 , 非侵入式的
③控制反转IOC , 面向切面Aop
④对事物的支持 , 对框架的支持
⑤一句话概括:Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的框架
1.3 组成
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.4 拓展
①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;
2. IOC基础
2.1 案例分析
一、传统写法:
①首先写一个UserDao接口
public interface UserDao {
void getUser();
}
②再写一个UserDao实现类
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("使用传统方法获取用户数据");
}
}
③最后写一个测试类
@Test
public void test(){
UserDao userDao = new UserDaoImpl();
userDao.getUser();//使用传统方法获取用户数据
}
④当需要增加UserDao实现类时
//UserDaoMySqlImpl实现UserDao
public class UserDaoMySqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("使用MySQL获取数据");
}
}
//UserDaoOracleImpl实现UserDao
public class UserDaoOracleImpl implements UserServer {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser(){
userDao.getUser("使用Oracle获取数据");
}
}
//分别实现测试类
@Test
public void test(){
UserDao userDao = new UserDaoMySqlImpl();
userDao.getUser();//使用MySQL获取数据
userDao userDao = new UserDaoOracleImpl();
userDao.getUser();//使用Oracle获取数据
}
二、利用Set实现:
①如果需要大量改动实现类,上述方法就不太适合使用;每次变动, 都需要修改大量代码,这种设计的耦合性太高了
② 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者;程序不用去管怎么创建,怎么实现了;它只负责提供一个接口这种思想 , 从本质上解决了问题 , 不用再去管理对象的创建了 , 更多的去关注业务的实现;耦合性大大降低,这也就是IOC的原型
public class UserServiceImpl implements UserServer {
private UserDao userDao;
//利用set实现
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
@Test
public void test(){
//使用MySQL实现
UserServiceImpl service = new UserServiceImpl();
service.setUserDao( new UserDaoMySqlImpl() );
service.getUser();
//使用Oracle实现
service.setUserDao(new UserDaoOracleImpl());
service.getUser();
}
}
2.2 IOC本质
①控制反转IOC(Inversion of Control),是一种设计思想;DI(依赖注入)是实现IOC的一种方法,也有人认为DI只是IOC的另一种说法;没有IOC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制;而控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了;
②IOC是Spring框架的核心内容,使用多种方式完美的实现了IOC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC;
③Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象;
④采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的;
⑤控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式;在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入;
3. HelloSpring
<!--Spring需要导入commons-logging进行日志记录,利用maven,自动下载对应的依赖项-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
①编写一个Hello实体类
public class Hello {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("Hello,"+ name );
}
}
②编写配置文件bean.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">
<!--bean就是java对象,由Spring创建和管理-->
<bean id="Hello" class="com.user.bean.Hello">
<property name="name" value="Spring"/>
</bean>
</beans>
③编写测试类
@Test
public void test(){
//解析beans.xml文件,生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//getBean:参数即为Spring配置文件中bean的id
Hello hello = (Hello) context.getBean("Hello");
hello.show();
}
④总结
1.Hello对象是由Spring创建的,属性是由Spring容器设置的;
2.控制:谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的;
3.反转 : 程序本身不创建对象 , 而变成被动的接收对象;
4.依赖注入 : 就是利用Set方法来进行注入的;
5.IOC是一种编程思想,由主动的编程变成被动的接收可以通过newClassPathXmlApplicationContext去浏览一下底层源码;
⑤添加案例
<?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="MysqlImpl" class="user.impl.UserDaoMySqlImpl"/>
<bean id="OracleImpl" class="user.impl.UserDaoOracleImpl"/>
<bean id="ServiceImpl" class="user.impl.UserServiceImpl">
<!--注意:这里的name并不是属性,而是Set方法后面的那部分,首字母小写-->
<!--引用另外一个bean,不是用value而是用 ref-->
<property name="userDao" ref="OracleImpl"/>
</bean>
</beans>
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
serviceImpl.getUser();
}
4. Spring配置
4.1 别名
alias 设置别名 , 为bean设置别名 , 可以设置多个别名
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="userT" alias="userNew"/>
4.2 Bean的配置
<!--bean就是java对象,由Spring创建和管理-->
<!--
1.id是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
2.如果配置id,又配置了name,那么name是别名
3.name可以设置多个别名,可以用逗号,分号,空格隔开
4.如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象
5.Class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.user.bean.Hello">
<property name="name" value="Spring"/>
</bean>
4.3 Import
<!--团队的合作使用import-->
<import resource="{path}/beans.xml"/>
5. DI
依赖 : 指Bean对象的创建依赖于容器,Bean对象的依赖资源
注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配
5.1 构造器注入
5.1.1 无参构造创建
①User
public class User {
private String name;
public User() {
System.out.println("User无参构造方法");
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
②Bean.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">
<!--bean就是java对象,由Spring创建和管理-->
<bean id="Hello" class="com.user.bean.Hello">
<property name="name" value="Spring"/>
</bean>
</beans>
③测试类
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//在执行getBean的时候,user已经创建好了,通过无参构造
User user = (User) context.getBean("user");
//调用对象的方法
user.show();
}
5.1.2 有参构造创建
①User
public class User {
private String name;
public User(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
}
②Bean.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">
<!-- 第一种根据index参数下标设置 -->
<bean id="user" class="com.user.bean.User">
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="xiaoguo"/>
</bean>
<!-- 第二种根据参数名字设置 -->
<bean id="user" class="com.user.bean.User">
<!-- name指参数名 -->
<constructor-arg name="name" value="xiaoguo"/>
</bean>
<!-- 第三种根据参数类型设置 -->
<bean id="user" class="com.user.bean.User">
<constructor-arg type="java.lang.String" value="xiaoguo"/>
</bean>
</beans>
③测试类
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = (User) context.getBean("user");
user.show();
}
5.2 Set注入
①要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写;
②如果属性是boolean类型, 没有set方法 , 是 is;
一、Address.java
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
二、Student.java
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
/*
*提供Get与Set方法
*/
}
三、测试类
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
}
5.2.1 常量注入
<bean id="student" class="com.com.user.bean.Student">
<property name="name" value="xiaoguo"/>
</bean>
5.2.2 Bean注入
<bean id="addr" class="com.user.pojo.Address">
<property name="address" value="济南"/>
</bean>
<bean id="student" class="com.user.pojo.Student">
<property name="name" value="xiaoguo"/>
<property name="address" ref="adressRef"/>
</bean>
5.2.3 数组注入
<bean id="student" class="com.user.pojo.Student">
<property name="id" ref="addressRef"/>
<property name="name" value="xiaoguo"/>
<property name="address">
<array>
<value>济南</value>
<value>青岛</value>
<value>威海</value>
</array>
</property>
</bean>
5.2.4 List注入
<property name="address">
<list>
<value>济南</value>
<value>青岛</value>
<value>威海</value>
<list>
</property>
5.2.5 Map注入
<property name="address">
<map>
<entry key="济南" value="433212"/>
<entry key="青岛" value="456682"/>
</map>
</property>
5.2.6 set注入
<property name="address">
<set>
<value>济南</value>
<value>青岛</value>
<value>威海</value>
</set>
</property>
5.2.7 Null注入
<property name="wife"><null/></property>
5.2.8 Properties注入
<property name="info">
<props>
<prop key="学号">2018020106</prop>
<prop key="性别">男</prop>
<prop key="姓名">XiaoGuo</prop>
</props>
</property>
5.2.9 约束注入
一、User类
public class User {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +"name='" + name + '\'' +", age=" + age +'}';
}
}
二、C命名空间注入
<!--需要在头文件中添加约束-->
<!--c就是所谓的构造器注入-->
xmlns:c="http://www.springframework.org/schema/c"
<!--C(构造:Constructor)命名空间,属性依然要设置set方法-->
<bean id="user" class="com.user.pojo.User" c:name="xiaoguo"/>
三、P命名空间注入
<!--需要在头文件中添加约束-->
xmlns:p="http://www.springframework.org/schema/p"
<!--P(属性:properties)命名空间,属性依然要设置set方法-->
<bean id="user" class="com.user.pojo.User" p:name="xiaoguo"/>
四、测试类
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
5.2.10 属性注入
①可以不用提供set方法,直接在直接名上添加@value(“值”)
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
@Component("user")
public class User {
@Value("xiaoguo")
// 相当于配置文件中 <property name="name" value="xiaoguo"/>
public String name;
}
②如果提供了set方法,在set方法上添加@value(“值”)
@Component("user")
public class User {
public String name;
@Value("xiaoguo")
public void setName(String name) {
this.name = name;
}
}
5.3 Bean的作用域
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean;简单地讲,bean就是由IoC容器初始化、装配及管理的对象
类别 | 说明 |
---|---|
singleton | 在Spring IOC容器中仅存在一个Bean实例,Bean以单例方式存在;默认值为singleton; |
prototype | 每次从Spring IOC容器中调用Bean时,都会返回一个新的实例;即每次调用getBean()时,相当于执行new xxxBean(); |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境; |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext环境; |
5.3.1 Singleton
①当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例;
②所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例;
③Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管是否使用;它都存在,每次获取到的对象都是同一个对象;
④注意,Singleton作用域是Spring中的缺省作用域;要在XML中将bean定义成singleton,可以这样配置:
<bean id="ServiceImpl" class="com.user.service.ServiceImpl" scope="singleton">
@Test
public void test03(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//当scope="singleton"时,Spring IoC容器中只会存在一个共享的bean实例
User user = (User) context.getBean("user");
User users = (User) context.getBean("user");
System.out.println(user==users);//true
}
5.3.2 Prototype
①当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例;
②Prototype作用域的bean会导致在每次对该bean请求时都会创建一个新的bean实例;
③Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象;
④根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域;在XML中将bean定义成prototype,可以这样配置:
<!--两种方式均可-->
<bean id="user" class="user.bean.User" scope="prototype">
<bean id="user" class="user.bean.User" singletion="false">
@Test
public void test03(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//当scope="prototype"时,Spring IoC容器会导致在每次对该bean请求时都会创建一个新的bean实例
User user = (User) context.getBean("user");
User users = (User) context.getBean("user");
System.out.println(user==users);//false
}
5.3.3 Request
①当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;
②即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成;
③针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态;而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化;当处理请求结束,request作用域的bean实例将被销毁;
④该作用域仅在基于web的SpringApplicationContext情形下有效,考虑下面bean定义:
<bean id="user" class="user.bean.User" scope="request">
5.3.4 Session
①当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例;
②针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效;
③与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化;当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉;
④该作用域仅在基于web的Spring ApplicationContext情形下有效,考虑下面bean定义:
<bean id="user" class="user.bean.User" scope="session">
6. Bean的自动装配
一、自动装配是使用spring满足bean依赖的一种方法
二、Spring会在应用上下文中为某个bean寻找其依赖的bean
三、Spring中bean有三种装配机制:①在xml中显式配置 ②在java中显式配置 ③自动化的装配bean
四、推荐不使用自动装配xml配置 , 而使用注解
五、Spring的自动装配需要从两个角度来实现或者说是两个操作:
①组件扫描:Spring自动发现应用上下文中所创建的bean
②自动装配:Spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
6.1 测试环境搭建
一、新建两个实体类
public class Cat {
public void shout() {
System.out.println("Cat实体类");
}
}
public class Dog {
public void shout() {
System.out.println("Dog实体类");
}
}
二、新建用户类
public class User {
private Cat cat;
private Dog dog;
private String str;
/*
*提供Get与Set方法
*/
}
三、新建Bean.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">
<bean id="dog" class="com.user.bean.Dog"/>
<bean id="cat" class="com.user.bean.Cat"/>
<bean id="user" class="com.user.bean.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="str" value="xiaoguo"/>
</bean>
</beans>
四、测试类
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = (User) context.getBean("user");
user.getCat().shout();//Cat实体类
user.getDog().shout();//Dog实体类
}
6.2 byName
一、由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低;采用自动装配将避免这些错误,并且使配置简单化;修改bean配置,增加一个属性 autowire=“byName”;
<bean id="user" class="user.bean.User" autowire="byName">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="str" value="xiaoguo"/>
</bean>
二、当一个bean节点带有 Autowire byName的属性时:
①将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat;
②去Spring容器中寻找是否有此字符串名称id的对象;
③如果有,就取出注入;如果没有,就报空指针异常;
6.3 byType
一、使用Autowire byType首先需要保证:同一类型的对象,在Spring容器中唯一;如果不唯一,会报NoUniqueBeanDefinitionException的异常
二、因为是按类型装配,将Cat的bean名称改掉,甚至将id属性去掉,也不影响结果;
<bean id="dog" class="user.bean.Dog"/>
<bean id="cat" class="user.bean.Cat"/>
<bean id="cat2" class="user.bean.Cat"/>
<bean id="user" class="user.bean.User" autowire="byType">
<property name="str" value="xiaoguo"/>
</bean>
6.4 注解装配
JDK1.5开始支持注解,Spring2.5开始全面支持注解
<!--在Spring配置文件中引入context文件头-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:context="http://www.springframework.org/schema/context/spring-context.xsd"
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"
>
<!--开启属性注解支持-->
<context:annotation-config/>
</beans>
6.4.1 @Autowired
①@Autowired是按类型自动转配的,不支持id匹配;
②需要导入 spring-aop的包;
③@Autowired(required=false) 说明对象可以为null;为true时对象必须存对象,不能为null;默认为true;
//将User类中的set方法去掉,使用@Autowired注解
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String str;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getStr() {
return str;
}
}
<!--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"
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">
<!-- 注解支持 -->
<context:annotation-config/>
<bean id="dog" class="user.bean.Dog"/>
<bean id="cat" class="user.bean.Cat"/>
<bean id="user" class="user.bean.User"/>
</beans>
//测试类
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = (User) context.getBean("user");
user.getCat().shout();//Cat实体类
user.getDog().shout();//Dog实体类
}
6.4.2 @Qualifier
①@Autowired是根据类型自动装配的,加上@Qualififier则可以根据byName的方式自动装配
②@Qualififier不能单独使用
一、修改bean.xml文件
<!--bean id修改为不为类的默认名字-->
<bean id="dog1" class="user.bean.Dog"/>
<bean id="dog2" class="user.bean.Dog"/>
<bean id="cat1" class="user.bean.Cat"/>
<bean id="cat2" class="user.bean.Cat"/>
二、不添加@Qualifier
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = (User) context.getBean("user");
//NoUniqueBeanDefinitionException
//No qualifying bean of type 'user.bean.Cat' available: expected single matching bean but found 2: cat1,cat2
user.getCat().shout();
user.getDog().shout();
}
}
三、添加@Qualifier
public class User {
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = (User) context.getBean("user");
user.getCat().shout();//Cat实体类
user.getDog().shout();//Dog实体类
}
}
6.4.3 @Resource
①@Resource如有指定的name属性,先按该属性进行byName方式查找装配;
②其次再进行默认的byName方式进行装配;
③如果以上都不成功,则按byType的方式自动装配;
④都不成功,则报异常;
一、测试案例
1.实体类
//如果允许对象为null,设置required = false,默认为true
public class User {
@Autowired
@Resource(name = "cat2")
private Cat cat;
@Autowired
private Dog dog;
private String str;
}
2.修改bean.xml
<bean id="dog2" class="user.bean.Dog"/>
<bean id="dog3" class="user.bean.Dog"/>
<bean id="cat2" class="user.bean.Cat"/>
<bean id="cat3" class="user.bean.Cat"/>
<bean id="user" class="user.bean.User"/>
3.测试类
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = (User) context.getBean("user");
//NoUniqueBeanDefinitionException
user.getCat().shout();
user.getDog().shout();
}
二、修改测试案例
1.实体类
public class User {
@Autowired
@Resource
private Cat cat;
@Autowired
@Resource
private Dog dog;
private String str;
}
2.修改bean.xml
<bean id="dog" class="user.bean.Dog"/>
<bean id="cat1" class="user.bean.Cat"/>
<bean id="user" class="user.bean.User"/>
3.测试类
//进行byName查找后失败,再进行byType后查找成功
public class User {
@Autowired
@Resource
private Cat cat;
@Autowired
private Dog dog;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = (User) context.getBean("user");
user.getCat().shout();//Cat实体类
user.getDog().shout();//Dog实体类
}
}
6.4.4 三者区别
①@Autowired与@Resource都可以用来装配bean,都可以写在字段上,或写在Setter方法上;
②它们的作用相同,都是用注解方式注入对象,但执行顺序不同;@Autowired先byType,@Resource先byName;
③@Autowired默认按类型装配(属于Spring规范),默认情况下要求依赖对象必须存在;如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ;如果我们想使用名称装配可以结合@Qualifier注解进行使用;
④@Resource,默认按照Name进行装配,名称可以通过name属性进行指定;如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在Setter方法上默认取属性名进行装配; 当找不到与名称匹配的bean时才按照类型进行装配;但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配;
6.4.5 衍生注解
@Component三个衍生注解,目的是为了更好的进行分层;Spring可以使用其它三个注解,目前使用哪一个功能都一样,写上这些注解,就相当于将这个类交给Spring管理装配了:@Controller:web层 、@Service:service层 、@Repository:dao层
6.5 JavaConfig
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能;
一、编写实体类
@Component //将这个类标注为Spring的一个组件,放到容器中
public class Dog {
public String name = "dog";
}
@Configuration //代表这是一个配置类
public class Cat {
}
二、编写MyConfig配置类
@Configuration //代表这是一个配置类
@Import(Cat.class) //导入合并其他配置类,类似于配置文件中的inculde标签
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id
public Dog dog(){
return new Dog();
}
}
三、测试类
@Test
public void test(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = (Dog) applicationContext.getBean("dog");
System.out.println(dog.name);//dog
}
7. 代理模式
7.1 静态代理
一、优点:①可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情;②公共的业务由代理来完成,实现了业务的分工;③公共业务发生扩展时变得更加集中和方便;
二、缺点: 多了代理类 , 工作量变大了,开发效率降低;
7.1.1 静态角色分析
①抽象角色 : 一般使用接口或者抽象类来实现
②真实角色 : 被代理的角色
③代理角色 : 代理真实角色后 , 一般会做一些附属的操作
④客户 : 使用代理角色来进行一些操作
7.1.2 代码实现
一、创建接口UserService
//抽象角色:增删改查业务
public interface UserService {
void add();
void delete();
void update();
void query();
}
二、创建实现类UserServiceImpl
//真实对象:完成增删改查操作的人
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 query() {
System.out.println("真实业务:查找一个用户");
}
}
三、创建代理类添加业务
//代理角色:实现日志功能
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
public void log(String message){
System.out.println("代理类:执行"+message+"方法");
}
}
四、创建测试类
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//使用代理类实现日志功能
proxy.setUserService(userService);
//先调代理类的add方法,再由代理类的add方法自动调用真实对象的add方法
proxy.add();
}
}
/*main:
代理类:执行add方法
真实业务:添加一个用户
Process finished with exit code 0*/
7.2 动态代理
一、核心:①一个动态代理,一般代理某一类业务;②一个动态代理可以代理多个类,代理的是接口;
二、动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
①基于接口的动态代理----JDK动态代理
②基于类的动态代理----Cglib动态代理
③现在用的比较多的是 Javasist来生成动态代理
7.2.1 动态角色分析
①动态代理的角色和静态代理的一样
②动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的
③JDK的动态代理需要了解两个核心类 : InvocationHandler 和 Proxy
一、InvocationHandler:调用处理程序
InvocationHandler是由代理实例的调用程序实现的接口,每个代理都有程序相关联的调用处理程序;
//参数
//proxy:调用该方法的代理实例
//method:所述方法对应于调用代理实例上的接口方法的实例;方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口;
//args:包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数;原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean;
Object invoke(Object proxy, 方法 method, Object[] args);
二、Proxy:代理
Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的父类;
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
7.2.2 代码实现
一、创建抽象类Rent
//抽象角色:租房
public interface Rent {
void rent();
}
二、创建真实角色Host
//真实角色:房东(出租房子)
public class Host implements Rent {
public void rent() {
System.out.println("房屋出租");
}
}
三、创建动态代理
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
//生成代理类
//重点是第二个参数,获取要代理的抽象角色,之前都是一个角色,现在可以代理一类角色
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
//proxy:代理类
//method:代理类的调用处理程序的方法对象
//处理代理实例上的方法调用并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本质利用反射实现
Object result = method.invoke(rent, args);
fare();
return result;
}
}
四、创建测试类
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理实例的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host); //将真实角色放置进去
Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类
proxy.rent();
}
}
//带房客看房
//房屋出租
//收中介费
8. AOP
8.1 概述
①AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术;
②AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型;
③利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率;
8.2 AOP在Spring中的作用
一、提供声明式事务;
二、允许用户自定义切面:
①横切关注点:跨越应用程序多个模块的方法或功能;即与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点;如日志 , 安全 , 缓存 , 事务等
②切面(ASPECT):横切关注点 被模块化 的特殊对象;即,它是一个类
③通知(Advice):切面必须要完成的工作;即,它是类中的一个方法
④目标(Target):被通知对象
⑤代理(Proxy):向目标对象应用通知之后创建的对象
⑥切入点(PointCut):切面通知 执行的 “地点”的定义
⑦连接点(JointPoint):与切入点匹配的执行点
三、SpringAOP中通过Advice定义横切逻辑,即Aop在不改变原有代码的情况下,去增加新的功能;Spring中支持5种类型的Advice:
通知类型 | 连接点 | 实现接口 |
---|---|---|
前置通知 | 方法前 | MethodBeforeAdvice |
后置通知 | 方法后 | AfterReturningAdvice |
环绕通知 | 方法前后 | MethodInterceptor |
异常抛出通知 | 方法抛出异常 | ThrowsAdvice |
引介通知 | 类中新增新的方法属性 | IntroductionInterceptor |
8.3 Spring实现Aop
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
8.3.1 Spring API实现
一、创建UserService接口
public interface UserService {
void add();
void delete();
void update();
void search();
}
二、创建UserServiceImpl实现类
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("查询用户");
}
}
三、创建前置增强类Log
public class Log implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//objects:被调用的方法的参数
//Object:目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
四、创建后置增强类AfterLog
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args被调用的方法的对象的参数
//target被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," +"返回值:"+returnValue);
}
}
五、修改bean.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: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">
<!--注册bean-->
<bean id="userService" class="user.server.UserServiceImpl"/>
<bean id="log" class="user.server.Log"/>
<bean id="afterLog" class="user.server.AfterLog"/>
<!--aop的配置-->
<aop:config>
<!--切入点expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* user.server.UserServiceImpl.*(..))"/>
<!--执行环绕:advice-ref执行方法;pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
六、创建测试类Test
Spring的Aop就是将公共的业务(日志、安全等)和领域业务结合起来,当执行领域业务时,将会把公共业务加进来;实现公共业务的重复利用,领域业务更纯粹以便专注领域业务,其本质还是动态代理;
public class Test {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = (UserService) context.getBean("userService");
userService.search();
}
}
8.3.2 自定义类实现AOP
一、目标业务类不变依旧是UserServiceImpl
二、创建切入类DiyPointcut
public class DiyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
三、修改bean.xml文件
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="user.server.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* user.server.userServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
四、创建测试类
public class Test {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
8.3.3 使用注解实现
一、创建增强类AnnotationPointcut
@Aspect
public class AnnotationPointcut {
@Before("execution(* user.server.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* user.server.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* user.server.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
二、修改bean.xml
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="user.server.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
9. Spring事务
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制;Spring支持编程式事务管理和声明式的事务管理;
9.1 编程式事务管理
①将事务管理代码嵌到业务方法中来控制事务的提交和回滚
②缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
9.2 声明式事务管理
①一般情况下比编程式事务好用
②将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理
③将事务管理作为横切关注点,通过AOP方法模块化;Spring中通过Spring AOP框架支持声明式事务管理;
9.3 事务管理器
①无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的
②就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法
<!--JDBC事务管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置aop织入事务-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* user.impl.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
9.4 Spring事务传播特性
一、Spring 默认的事务传播行为是PROPAGATION_REQUIRED,它适合于绝大多数的情况
二、事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播;Spring支持7种事务传播行为:
①propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择
②propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行
③propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常
④propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起
⑤propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
⑥propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常
⑦propagation_nested:如果当前存在事务,则在嵌套事务内执行;如果没有事务,则执行与propagation_required类似的操作