初识 Spring
文章目录
一、学习任务
- 编写Spring程序,实现控制台输出的功能
- 使用Spring框架实现打印机功能
- 使用Spring AOP实现日志功能
二、本章目标
- 理解Spring IoC原理
- 掌握Spring IoC配置
- 理解Spring AOP原理
- 掌握Spring AOP配置
三、Spring框架简介
Spring 英文单词词意:春天
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
官网 : http://spring.io/
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects
Spring是Java EE编程领域的一个轻量级开源J2EE应用程序框架,该框架由一个叫Rod Johnson的程序员在 2002 年最早提出并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架,很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学 。
Spring是一个开源容器框架,它集成各类型的工具,通过核心的
Bean factory
实现了底层的类的实例化和生命周期的管理。在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理,包括动态加载和切面编程
。 Spring是独特的,因为若干个原因:它定位的领域是许多其他流行的framework没有的。Spring致力于提供一种方法管理你的业务对象。
Spring是全面的和模块化的。Spring有分层的体系结构,这意味着你能选择使用它孤立的任何部分,它的架构仍然是内在稳定的。例如,你可能选择仅仅使用Spring来简单化JDBC的使用,或用来管理所有的业务对象。它的设计从底部帮助你编写易于测试的代码。Spring是用于测试驱动工程的理想的framework。
Spring对你的工程来说,它不需要一个以上的framework。Spring是潜在地一站式解决方案,定位于与典型应用相关的大部分基础结构。它也涉及到其他framework没有考虑到的内容。Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。
Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC,在接下来的课程中将会给大家详细讲解。
3.1.Spring框架作用与优点
作用:
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
企业应用开发的"一站式"选择,贯穿于表现层、业务层、持久层。
优点:
- Spring是一个开源免费的框架 , 容器 .
- Spring是一个轻量级的框架 , 非侵入式的 .
- 控制反转 IoC , 面向切面 Aop
- 对事物的支持 , 对框架的支持良好整合
3.2.Spring框架体系结构介绍
Spring体系结构图
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。
3.3.Spring框架设计理念与核心技术
Spring设计理念
【是面向Bean的编程】
Spring两大核心技术
- 控制反转(IoC:Inversion of Control)/依赖注入(DI:Dependency Injection)
- 面向切面编程(AOP:Aspect Oriented Programming)
四、控制反转/依赖注入
4.1.控制反转/依赖注入
问题:
- 在学习JSP分层模式中,用户模块业务层调用数据层,用户模块业务层与数据层高度耦合,怎么解决?
解决方案1:
步骤一:先写一个UserDao接口
/** * 用户DAO接口 * @author Aiden */ public interface UserDao { /** * 新增用户信息 * @param user 用户对象 * @return int 受影响的行数 */ int insert(User user); }
步骤二:再去写UserDao的实现类
public class UserDaoMySqlImpl implements UserDao { //日志 private Logger logger = Logger.getLogger(UserDaoMySqlImpl.class); @Override public int insert(User user) { //模拟MySql数据库存储操作 logger.info("连接MySql数据库..."); logger.info("正在保存信息..."); logger.info("用户信息" + user.toString() + "保存入库。"); return 1;//默认标识成功 } }
步骤三:然后去写UserService的接口
/** * 用户业务接口 * @author Aiden */ public interface UserService { /** * 新增用户信息 * @param user 用户对象 * @return int 受影响的行数 */ int insert(User user); }
步骤四:最后写UserService的实现类
/** * 用户业务实现类 * @author Aiden */ public class UserServiceImpl implements UserService { //日志 private Logger logger = Logger.getLogger(UserService.class); //实例化需要依赖的UserDao实例对象 private UserDao userDao = new UserDaoMySqlImpl(); @Override public int insert(User user) { return userDao.insert(user); } }
步骤五:单元测试一下
@Test public void testAddUser() { User user = new User("张三", "男"); //用户业务实例 UserService userService = new UserServiceImpl(); //调用新增方法 int result = userService.insert(user); }
设计缺陷:
由以上代码不难看出在用户业务实现类中高度依赖UserDaoMySqlImpl实现类,但是现在如果我们要使用Oracle 或者MSSQL, 那么又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类对吧 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 。
解决方案2:
- 使用set方法实现解耦
public class UserServiceImpl implements UserService { private UserDao userDao; //利用set实现 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public int insert(User user) { return userDao.insert(user); } }
@Test public void testAddUser(){ User user = new User("张三", "男"); UserService service = new UserServiceImpl(); //①设置mysql数据库 service.setUserDao(new UserDaoMySqlImpl() ); //调用新增方法 int result1 = userService.insert(user); //②设置oracle数据库 service.setUserDao(new UserDaoOracleImpl()); //调用新增方法 int result2 = userService.insert(user); }
- 工厂设计模式实现解耦
/** * UserDao工厂类 * @author Aiden */ public class UserDaoFactory { /** * 简单工厂 * @param dbType 数据库类型 * @return UserDao */ public static UserDao getInstance(String dbType){ switch (dbType) { case "mysql": return new UserDaoMySqlImpl(); case "oracle": return new UserDaoOracleImpl(); case "mssql": return new UserDaoMSSqlImpl(); default: throw new RuntimeException("无效的数据库类型:"+dbType+" ,DAO获取失败"); } } }
/** * 用户业务实现类 * * @author Aiden */ public class UserServiceImpl implements UserService { // 通过工厂获取所依赖的 UserDao 对象 private UserDao dao = UserDaoFactory.getInstance(); … }
代码改造后总结:
1.通过以上的代码改造,仔细去思考一下 , 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 。
2.这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型!
4.2控制反转/依赖注入
控制反转(IoC)
- 将创建对象的控制权转移,是一种程序设计思想。
依赖注入(DI)
- 将依赖的对象注入到需要的类中去,是"控制反转"设计思想的具体实现方式。
- 控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方。
- IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
- Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
- 采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
- 控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
五、Spring实现"控制反转"
问题:
- 如何使用Spring实现"控制反转" ?
需求:
- 在控制台输出 “反转的人生,如此惊艳”
分析:
- 创建maven项目添加所需要的依赖jar包。
- 编写Spring核心配置文件
- 编写测试代码通过Spring进行属性注入
步骤:
1、添加maven配置: spring 需要导入commons-logging进行日志记录 。利用maven , 他会自动下载对应的依赖项 。
<!--spring-webmvc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.3.RELEASE</version> </dependency>
2、添加log4j配置文件:log4j.properties
# rootLogger是所有日志的根日志,修改该日志属性将对所有日志起作用 # 下面的属性配置中,所有日志的输出级别是info,输出源是con log4j.rootLogger=info,con # 定义输出源的输出位置是控制台 log4j.appender.con=org.apache.log4j.ConsoleAppender # 定义输出日志的布局采用的类 log4j.appender.con.layout=org.apache.log4j.PatternLayout # 定义日志输出布局 log4j.appender.con.layout.ConversionPattern=%d{MM-dd HH:mm:ss}[%p]%c%n -%m%n
3、编写一个Hello实体类
/** * 第一个Spring程序,输出"反转的人生,如此惊艳!"。 */ public class HelloSpring { // 定义hello属性,该属性的值将通过Spring框架进行设置 private String hello = null; /** * 打印方法,从Spring配置文件中获取属性并输出。 */ public void print() { System.out.println("Spring say:," + this.getHello() + "!"); } public String getHello() { return hello; } public void setHello(String hello) { this.hello = hello; } }
4、Spring核心配置文件:applicationContext.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元素声明需要Spring创建的实例。该实例的类型通过class属性指定,并通过id属性为该实例指定一个名称,以便在程序中使用 --> <bean id="helloSpring" class="com.aiden.pojo.HelloSpring"> <!-- property元素用来为实例的属性赋值,此处实际是调用setHello()方法实现赋值操作 --> <property name="hello"> <!-- 此处将字符串"Spring"赋值给hello属性 --> <value>反转的人生,如此惊艳</value> </property> </bean> </beans>
5、单元测试类:HelloSpringTest
public class HelloSpringTest { @Test public void helloSpring() { // 1.通过ClassPathXmlApplicationContext实例化Spring的上下文 ApplicationContext context = new ClassPathXmlApplicationContext( "applicationContext.xml"); // 2.通过ApplicationContext的getBean()方法,根据id来获取bean的实例 HelloSpring helloSpring = context.getBean("helloSpring",HelloSpring.class); // 3.执行print()方法 helloSpring.print(); } }
思考:
- Hello 对象是谁创建的 ? hello 对象是由Spring创建的
- Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的
结论:
如上这个过程就叫控制反转 :
- 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的。
- 反转 : 程序本身不创建对象 , 而变成被动的接收对象 。
六、IOC创建对象的方式
方式1:通过无参构造方法来创建
<?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="user" class="com.aiden.pojo.User"> <property name="name" value="朱美亮"/> </bean> </beans>
方式2:通过有参构造方法来创建
<!-- 第一种根据index参数下标设置 --> <bean id="user" class="User"> <!-- index指构造方法 , 下标从0开始 --> <constructor-arg index="0" value="刘旭"/> </bean> <!-- 第二种根据参数名字设置 --> <bean id="user" class="com.aiden.pojo.User"> <!-- name指参数名 --> <constructor-arg name="fullName" value="刘旭"/> </bean> <!-- 第三种根据参数类型设置 --> <bean id="user" class="com.aiden.pojo.User"> <constructor-arg type="java.lang.String" value="刘旭"/> </bean>
对应的实体类
package com.aiden.pojo; import java.io.Serializable; /** * 用户信息实体类 * @author Aiden */ public class User implements Serializable { private Integer userId;//编号 private String fullName;//姓名 //构造函数1 public User() { System.out.println("User无参构造方法"); } //构造函数2 public User(String fullName) { this.fullName = fullName; } //构造函数3 public User(String fullName, String gender) { this.fullName = fullName; this.gender = gender; } //省略getter/setter... }
七、Spring常用配置介绍
1.别名
- Spring中我们可以通过 alias 属性设置bean的别名 , 并且支持设置多个别名,语法如下:
<!--设置别名:在获取Bean的时候可以使用别名获取--> <alias name="user" alias="userNew"/>
2.Bean的配置
- id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符,class是bean的全限定名=包名+类名
- 如果配置id,又配置了name,那么name是别名。
- name可以设置多个别名,可以用逗号,分号,空格隔开。
- 如果不配置id和name,可以根据
applicationContext.getBean(.class)
获取对象。<!--bean就是java对象,由Spring创建和管理--> <bean id="hello" name="hi,sayHi;hello1 hello2" class="com.aiden.pojo.Hello"> <property name="name" value="Spring"/> </bean>
3.import
- 实际开发中,项目团队的合作主要通过import 标签来实现bean的注入,如下语法所示,{path}标识bean的xml资源路径。
<import resource="{path}/beans.xml"/>
八、依赖注入(DI)
依赖注入(Dependency Injection,DI)。
依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
1、构造器注入
- 见主题六(IOC创建对象的方式)中介绍。
2、Set 注入 (本课重点)
注意事项: 这里我们需要注意的是被注入的属性或对象 , 必须提供set方法,且set方法的方法名称由set + 属性首字母大写组成, 如果属性是boolean类型 , 则没有set方法 , 是 is 。
步骤1: 定义Address地址类
package com.aiden.pojo; /** * 地址信息类 * * @author Aiden */ public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
步骤2: 定义Student学生信息类
package com.aiden.pojo; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; /** * 学生信息类 * * @author Aiden */ public class Student { private String name; //姓名 private Address address; //地址 private String[] musics; //喜欢的音乐 private List<String> hobbys; //爱好 private Map<String, String> card;//银行卡 private Set<String> games; //喜欢的游戏 private String phone; //手机号码 private Properties properties; //基础信息(学号、性别、出生日期) public void setName(String name) { this.name = name; } public void setAddress(Address address) { this.address = address; } public void setMusics(String[] musics) { this.musics = musics; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public void setCard(Map<String, String> card) { this.card = card; } public void setGames(Set<String> games) { this.games = games; } public void setPhone(String phone) { this.phone = phone; } public void setProperties(Properties properties) { this.properties = properties; } public void show() { System.out.println("姓名:" + this.name + "家庭住址:" + address.getAddress() + "喜欢的音乐:" ); for (String music : musics) System.out.print( music +"\t"); System.out.println("\n爱好:" + this.hobbys); System.out.println("银行卡:" + this.card); System.out.println("喜欢的游戏:" + this.games); System.out.println("手机号码:" + this.phone); System.out.println("基础信息:" + this.properties); } }
3、扩展的注入
3.1、常量注入
<bean id="student" class="com.aiden.pojo.Student"> <property name="name" value="小明"/> </bean>
@Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = context.getBean("student",Student.class); System.out.println(student.getName()); }
3.2、Bean注入
<bean id="addressObj" class="com.aiden.pojo.Address"> <property name="address" value="长沙"/> </bean> <bean id="student" class="com.aiden.pojo.Student"> <property name="name" value="小明"/> <!--注意点:这里的值是一个引用,ref--> <property name="address" ref="addressObj"/> </bean>
3.3、数组注入
<bean id="student" class="com.aiden.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addressObj"/> <property name="musics"> <array> <value>《中国心》</value> <value>《青花瓷》</value> <value>《我和你》</value> </array> </property> </bean>
3.4、List注入
<property name="hobbys"> <list> <value>阅读</value> <value>看电影</value> <value>听歌</value> </list> </property>
3.5、Map注入
<property name="card"> <map> <entry key="中国农业" value="6228280128069313663"/> <entry key="中国建设" value="621700166000758262"/> </map> </property>
3.6、set注入
<property name="games"> <set> <value>原神</value> <value>王者荣耀</value> <value>第五人格</value> </set> </property>
3.6、Null注入
<property name="phone"><null/></property>
3.7、Properties 注入
<property name="properties"> <props> <prop key="学号">20221008</prop> <prop key="性别">男</prop> <prop key="出生日期">2002年5月20日</prop> </props> </property>
3.8、P命名和C命名注入
/** * 用户信息 * @author Aiden */ 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 + '}'; } }
3.9.1.P命名空间注入 : 需要在头文件中加入约束文件
<!--导入约束 : xmlns:p="http://www.springframework.org/schema/p"--> <!--P(属性: properties)命名空间 , 直接注入属性--> <bean id="user" class="com.aiden.pojo.User" p:name="张三" p:age="20"/>
3.9.2.C 命名空间注入 : 需要在头文件中加入约束文件
<!--导入约束 : xmlns:c="http://www.springframework.org/schema/c"--> <!--C(构造: Constructor)命名空间 , 使用构造器注入--> <bean id="user" class="com.aiden.pojo.User" c:name="张三" c:age="20"/>
九、Bean的作用域(重点)
9.1.Bean的作用域
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 。
类别 说明 singleton 在Spring Ioc容器中仅存在一个Bean实例,Bean以单例方式存在 prototype 每次从父容器中调用Bean时,都会返回一个新的实例,即每次调用getBean( )时,相当于执行 new XxxBean( ) request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 session 同一个HTTP Session共享一个Bean,不同的session使用不同的Bean,仅适用于WebApplicationContext环境 注意: 几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
9.2.1 Singleton(单例模式)
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。
要在XML中将bean定义成singleton,可以这样配置:
<bean id="userServiceImpl" class="com.aiden.service.UserServiceImpl" scope="singleton">
测试:
@Test public void test03(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = context.getBean("user",User.class); User user2 = context.getBean("user",User.class); System.out.println(user==user2);//true }
9.2.2 Prototype(原型模式)
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。
根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> <!--或者--> <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
9.2.3 Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的SpringApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class="com.aiden.action.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
9.2.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也会被废弃掉。
十、Bean的自动装配
自动装配说明
- 自动装配是使用spring满足bean依赖的一种方法
- spring会在应用上下文中为某个bean寻找其依赖的bean。
Spring中bean三种装配机制:
- 在xml中显式配置;
- 在java中显式配置;
- 隐式的bean发现机制和自动装配。
本章节我们主要讲第三种: 自动化的装配bean。
Spring的自动装配需要从两个角度来实现:
- 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
**推荐不使用自动装配xml配置 , 而使用注解 **
测试环境搭建
步骤1:新建一个项目
步骤2:新建两个实体类Dog、Cat 都有一个吃食的方法
//猫类 public class Cat { public void eat() { System.out.println("小猫正在吃小鱼干..."); } } //狗狗类 public class Dog { public void eat() { System.out.println("小狗正在吃狗粮..."); } }
步骤3:新建一个用户类 User
public class User { private Cat cat; private Dog dog; private String fullName; }
步骤4:编写Spring配置文件注入User、Dog、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.aiden.pojo.Dog"/> <bean id="cat" class="com.aiden.pojo.Cat"/> <bean id="user" class="com.aiden.pojo.User"> <property name="cat" ref="cat"/> <property name="dog" ref="dog"/> <property name="fullName" value="张三"/> </bean> </beans>
步骤5:测试
public class MyTest { @Test public void testMethodAutowire() { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = context.getBean("user",User.class); user.getCat().eat(); user.getDog().eat(); } }
步骤6:查看运行结果
小猫正在吃小鱼干... 小狗正在吃狗粮...
byName
autowire byName (按名称自动装配)
我们在手动配置xml过程中,经常发生字母缺漏和大小写等错误,从而导致无法对其进行检查,使得开发效率降低,此时我们需要采用自动装配将避免这些错误,并且使配置简单化。
测试:
修改bean配置,增加一个属性 autowire=“byName”
<bean id="user" class="com.aiden.pojo.User" autowire="byName"> <property name="fullName" value="张三"/> </bean>
再次测试,结果依旧成功输出!
小猫正在吃小鱼干... 小狗正在吃狗粮...
我们将 cat 的 bean id 修改为 catXXX
再次测试, 执行时报空指针
java.lang.NullPointerException
。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。小结:
当一个bean节点带有 autowire byName的属性时。
- 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
- 去spring容器中寻找是否有此字符串名称id的对象。
- 如果有,就取出注入;如果没有,就报空指针异常。
byType
autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException
测试:
1、将user的bean配置修改一下 : autowire=“byType”
2、测试,正常输出
3、在注册一个cat 的bean对象!
<bean id="dog" class="com.aiden.pojo.Dog"/> <bean id="cat" class="com.aiden.pojo.Cat"/> <bean id="cat2" class="com.aiden.pojo.Cat"/> <bean id="user" class="com.aiden.pojo.User" autowire="byType"> <property name="fullName" value="张三"/> </bean>
4、测试,报错:
NoUniqueBeanDefinitionException
5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
这就是按照类型自动装配!
使用注解
jdk1.5开始支持注解,spring2.5开始全面支持注解。下章节讲解~
十一、Spring IoC实现打印机
使用Spring IoC实现JavaBean注入
问题:
- 如何"组装"一个打印机?
需求:
- 可使用彩色墨盒或灰色墨盒进行打印
- 可灵活配置打印页面的大小
- 打印机功能的实现依赖于墨盒和纸张
步骤分析:
- 定义接口规范,创建纸张和墨盒接口
- 开发打印功能
- 创建纸张、墨盒接口实现类
- 组装打印机
- 运行测试
1.printer包中定义墨盒、纸张接口、打印机程序类
package cn.printer; /** * 墨盒接口。 */ public interface Ink { /** * 定义打印采用的颜色的方法。 * @param red 红色值 * @param green 绿色值 * @param blue 蓝色值 * @return 返回打印采用的颜色 */ public String getColor(int red, int green, int blue); }
/** * 纸张接口 */ public interface Paper { public static final String newline = "\r\n"; /** * 输出一个字符到纸张。 */ public void putInChar(char c); /** * 获取输出到纸张上的内容。 */ public String getContent(); }
package cn.printer; /** * 打印机程序。 * 面向接口编程,而不是具体的实现类 */ public class Printer { private Ink ink = null; private Paper paper = null; /** * 设值注入所需的setter方法。 * @param ink 墨盒 */ public void setInk(Ink ink) { this.ink = ink; } /** * 设值注入所需的setter方法。 * @param paper 纸张 */ public void setPaper(Paper paper) { this.paper = paper; } /** * 打印方法 * @param message 要打印内容 */ public void print(String message) { // 输出颜色标记 System.out.println("使用" + ink.getColor(255, 200, 0) + "颜色打印:\n"); // 逐字符输出到纸张 for (int i = 0; i < message.length(); ++i) { paper.putInChar(message.charAt(i)); } // 将纸张的内容输出 System.out.print(paper.getContent()); } }
2.paper包中定义纸张实现类:
package cn.paper; import cn.printer.Paper; /** * 纸张实现类 */ public class TextPaper implements Paper { // 每行字符数 private int charPerLine = 16; // 每页行数 private int linePerPage = 5; // 纸张中内容 private String content = ""; // 当前横向位置,从0到charPerLine-1 private int posX = 0; // 当前行数,从0到linePerPage-1 private int posY = 0; // 当前页数 private int posP = 1; public String getContent() { String ret = this.content; // 补齐本页空行,并显示页码 if (!(posX == 0 && posY == 0)) { int count = linePerPage - posY; for (int i = 0; i < count; ++i) { ret += Paper.newline; } ret += "== 第" + posP + "页 =="; } return ret; } public void putInChar(char c) { content += c; ++posX; // 判断是否换行 if (posX == charPerLine) { content += Paper.newline; posX = 0; ++posY; } // 判断是否翻页 if (posY == linePerPage) { content += "== 第" + posP + "页 =="; content += Paper.newline + Paper.newline; posY = 0; ++posP; } } // setter方法,用于属性注入 public void setCharPerLine(int charPerLine) { this.charPerLine = charPerLine; } // setter方法,用于属性注入 public void setLinePerPage(int linePerPage) { this.linePerPage = linePerPage; } }
3.ink包中定义彩色、灰色墨盒实现类,该类实现Ink接口:
package cn.ink; import java.awt.Color; import cn.printer.Ink; /** * 彩色墨盒 */ public class ColorInk implements Ink { /** * 打印采彩色文字 */ public String getColor(int red, int green, int blue) { Color color = new Color(red, green, blue); return "#" + Integer.toHexString(color.getRGB()).substring(2); } }
package cn.ink; import java.awt.Color; import cn.printer.Ink; /** * 灰色墨盒 */ public class GreyInk implements Ink { /** * 打印采灰色文字 */ public String getColor(int red, int green, int blue) { int c = (red + green + blue) / 3; Color color = new Color(c, c, c); return "#" + Integer.toHexString(color.getRGB()).substring(2); } }
4.在spring核心配置中注入相应的属性和对象
<?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"> <!-- 定义A4纸张bean,该bean的id是a4Paper,class指定该bean实例的实现类 --> <bean id="a4Paper" class="cn.paper.TextPaper"> <!-- property元素用来指定需要容器注入的属性,charPerLine需要容器注入, TextPaper类必须拥有setCharPerLine()方法。 注入每行字符数 --> <property name="charPerLine" value="10" /> <!-- property元素用来指定需要容器注入的属性,linePerPage需要容器注入,TextPaper类必须拥有setLinePerPage()方法。 注入每页行数 --> <property name="linePerPage" value="8" /> </bean> <!-- 定义B5纸张bean,该bean的id是b5Paper,class指定该bean实例的实现类 --> <bean id="b5Paper" class="cn.paper.TextPaper"> <!-- property元素用来指定需要容器注入的属性,charPerLine需要容器注入, TextPaper类必须拥有setCharPerLine()方法。注入每行字符数 --> <property name="charPerLine" value="6" /> <!-- property元素用来指定需要容器注入的属性,linePerPage需要容器注入, TextPaper类必须拥有setLinePerPage()方法。注入每页行数 --> <property name="linePerPage" value="5" /> </bean> <!-- 定义彩色墨盒bean,该bean的id是colorInk,class指定该bean实例的实现类 --> <bean id="colorInk" class="cn.ink.ColorInk" /> <!-- 定义灰色墨盒bean,该bean的id是greyInk,class指定该bean实例的实现类 --> <bean id="greyInk" class="cn.ink.GreyInk" /> <!-- 组装打印机。定义打印机bean该bean的id是printer,class指定该bean实例的实现类 --> <bean id="printer" class="cn.printer.Printer"> <!-- 通过ref属性注入已经定义好的bean --> <!-- 注入彩色墨盒 --> <property name="ink" ref="colorInk"></property> <!-- 注入B5打印纸张 --> <property name="paper" ref="b5Paper"></property> </bean> </beans>
5.定义单元测试类测试打印机
package cn.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.printer.Printer; /** * 测试打印机。 */ public class PrinterTest { @Test public void printerTest() { ApplicationContext context = new ClassPathXmlApplicationContext( "applicationContext.xml"); // 通过Printer bean的id来获取Printer实例 Printer printer = (Printer) context.getBean("printer"); String content = "轻灵..轻灵的鼠标一点,神奇的世界出现。双手..把双手放在胸前" + ",敲打出美丽的画卷。轻灵..轻灵的鼠标一点,神奇的世界出现。双手..把双" + "手放在胸前,敲打出美丽的画卷。是谁让IT生活多彩绚烂,青鸟在我们身边," + "纵然是IT时代风云变幻,青鸟青鸟与我们相伴。飞翔吧青鸟,将知识在神州大" + "地撒遍;飞翔吧青鸟,让教育改变我们的生活。是你帮助了我们实现了心愿," + "又是你让我们的世界变成无限。因为有你,地更广阔,天更蓝"; printer.print(content); } }
十二、面向切面编程(AOP)
12.0.AOP在Spring中的作用
- 提供声明式事务;
- 允许用户自定义切面
12.1.面向切面编程
下图中的代码, 除了保存用户的业务功能外,还包含了事务处理、日志记录、异常处理等功能,怎样才能专注于业务功能处理呢?
12.2.面向切面编程(AOP)
AOP目标
- 让我们专注于业务功能处理
AOP原理
1)将复杂的需求分解出不同方面,将不同对象、不同模块之间的共同业务集中解决
2)通过动态代理的方式,把抽离出来的共性代码"织入"到业务代码中,实现对原有代码的增强处理
12.3.面向切面编程(AOP)
AOP相关术语
==横切关注点:==跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知 执行的 “地点”的定义。
连接点(JointPoint):与切入点匹配的执行点。
十三、Spring AOP在项目中的运用
13.1.Spring AOP在项目中的运用
需求:
- 使用Spring AOP实现日志输出
步骤
- maven中引入依赖的jar包
- 编写保存用户的业务代码以及用于增强处理的代码
- 编写Spring核心配置文件
- 运行测试
13.2.Spring AOP在项目中的运用4-2
目标方法
public class UserServiceImpl implements UserService { // …省略代码 public void save(User user){ userDao.saveUser(user); } }
增强处理
public class UserServiceLogger { private static final Logger log = Logger.getLogger(UserServiceLogger.class); //前置增前 public void before(JoinPoint jp) { log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法。方法入参:" + Arrays.toString(jp.getArgs())); } //后置增强 public void afterReturning(JoinPoint jp, Object result) { log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法。方法返回值:" + result); } }
13.3.Spring AOP在项目中的运用
定义切入点
- 切入点:简单的说,就是连接点的查询条件
<aop:config> <!-- execution:切入点表示式,符合该表达式的方法可以被织入增强处理--> <aop:pointcut id = "pointcut" expression = "execution(public void save(entity.User))"/> </aop:config>
表达式匹配规则举例
public * addNewUser(entity.User):"*" //表示匹配所有类型的返回值 public void *(entity.User):"*" //表示匹配所有方法名 public void addNewUser(..):".." //表示匹配任意参数个数和类型 * com.service.*.*(..) //匹配com.service包下所有类的所有方法 * com.service..*.*(..) //匹配com.service包及其子包下所有类的所有方法
13.4.Spring AOP在项目中的运用
<aop:config> <!----> <aop:pointcut id = "pointcut" expression = "execution(public void save(entity.User))"/> <!--ref="theLogger": 增强处理对象--> <aop:aspect ref="theLogger" > <!--method="before":前置增强处理方法,pointcut-ref="pointcut":切入点--> <aop:before method="before" pointcut-ref="pointcut" /> <!--method="afterReturning":后置增强处理方法,pointcut-ref="pointcut":切入点, returning="result": 指定需要注入返回值的参数名为result--> <aop:after-returning method="afterReturning" pointcut-ref = "point" returning="result" /> </aop:aspect> </aop:config>
maven中添加aop依赖包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> <scope>runtime</scope> </dependency>
log4j.properties
# rootLogger是所有日志的根日志,修改该日志属性将对所有日志起作用 # 下面的属性配置中,所有日志的输出级别是info,输出源是con log4j.rootLogger=info,con # 定义输出源的输出位置是控制台 log4j.appender.con=org.apache.log4j.ConsoleAppender # 定义输出日志的布局采用的类 log4j.appender.con.layout=org.apache.log4j.PatternLayout # 定义日志输出布局 log4j.appender.con.layout.ConversionPattern=%d{MM-dd HH:mm:ss}[%p]%c%n -%m%n
entity包
package entity; /** * 用户实体类 */ public class User implements java.io.Serializable { private Integer id; // 用户ID private String username; // 用户名 private String password; // 密码 private String email; // 电子邮件 // getter & setter public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
dao包
package dao; import entity.User; /** * 增加DAO接口,定义了所需的持久化方法 */ public interface UserDao { public void saveUser(User user); }
dao.impl包
package dao.impl; import dao.UserDao; import entity.User; /** * 用户DAO类,实现IDao接口,负责User类的持久化操作 */ public class UserDaoImpl implements UserDao { public void saveUser(User user) { // 这里并未实现完整的数据库操作,仅为说明问题 System.out.println("保存用户信息到数据库"); } }
service包
package service; import entity.User; /** * 用户业务接口,定义了所需的业务方法 */ public interface UserService { public void save(User user); }
service.impl包
package service.impl; import service.UserService; import dao.UserDao; import entity.User; /** * 用户业务类,实现对User功能的业务管理 */ public class UserServiceImpl implements UserService { /** * 声明接口类型的引用,和具体实现类解耦合 */ private UserDao userDao; /** * 保存用户 */ public void save(User user) { // 调用用户DAO的方法保存用户信息 userDao.saveUser(user); } /** * dao 属性的setter访问器,会被Spring调用,实现设值注入 */ public void setUserDao(UserDao userDao) { this.userDao = userDao; } }
aop包
package aop; import java.util.Arrays; import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; /** * 增强处理 * 提取的公共日志代码 */ public class UserServiceLogger { private static final Logger log = Logger.getLogger(UserServiceLogger.class); public void before(JoinPoint jp) { log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs())); } public void afterReturning(JoinPoint jp, Object result) { log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法。方法返回值:" + result); } }
spring核心配置文件
<?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 id="userDao" class="dao.impl.UserDaoImpl"></bean> <bean id="userService" class="service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <!-- 声明增强方法所在的Bean --> <bean id="theLogger" class="aop.UserServiceLogger"></bean> <!-- 配置切面 --> <aop:config> <!-- 定义切入点 --> <aop:pointcut id="pointcut" expression="execution(public void save(entity.User))" /> <!-- 引用包含增强方法的Bean --> <aop:aspect ref="theLogger"> <!-- 将before()方法定义为前置增强并引用pointcut切入点 --> <aop:before method="before" pointcut-ref="pointcut"/> <!-- 将afterReturning()方法定义为后置增强并引用pointcut切入点 --> <!-- 通过returning属性指定为名为result的参数注入返回值 --> <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result" /> </aop:aspect> </aop:config> </beans>
测试类AopTest
package test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.UserService; import service.impl.UserServiceImpl; import entity.User; public class AopTest { @Test public void aopTest() { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService service = (UserService) ctx.getBean("userService"); User user = new User(); user.setId(1); user.setUsername("test"); user.setPassword("123456"); user.setEmail("test@163.com"); service.save(user); } }
十四、本章总结
常见错误
解决方案:
proxy-target-class属性是用来配置AOP产生代理对象时的代理模式的。
springAOP的代理方法无所谓是注解还是XML配置,默认情况下都是查看目标类是否有实现任何接口,如果实现接口则使用JDK代理方式,如果没有实现接口,就使用CGLib方式。
当然有些情况下我们可能需要强制要求使用CGLib,这时在XML配置方式我们可以使用aop:aspectj-autoproxy属性proxy-target-class修改为true,强行要求spring使用CGLib进行动态代理。