1、概述
Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。
Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首次在 Apache 2.0 许可下发布。
Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。
Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO 编程模型来促进良好的编程实践。
三层架构:
- 表现层 web 层 (MVC是表现层的一个设计模型 )
- 业务层 service 层
- 持久层 dao 层
1.1、Spring 的优良特性
- 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
- 控制反转:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。
- 依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。
- 面向切面编程:Aspect Oriented Programming——AOP
- 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
- 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
- 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)
1.2、使用 Spring 框架的好处
Spring 框架主要的好处:
- Spring 可以使开发人员使用 POJOs 开发企业级的应用程序。只使用 POJOs 的好处是你不需要一个 EJB 容器产品,比如一个应用程序服务器,但是你可以选择使用一个健壮的 servlet 容器,比如 Tomcat 或者一些商业产品。
- Spring 在一个单元模式中是有组织的。即使包和类的数量非常大,你只要担心你需要的,而其它的就可以忽略了。
- Spring 不会让你白费力气做重复工作,它真正的利用了一些现有的技术,像 ORM 框架、日志框架、JEE、Quartz 和 JDK 计时器,其他视图技术。
- 测试一个用 Spring 编写的应用程序很容易,因为环境相关的代码被移动到这个框架中。此外,通过使用 JavaBean-style POJOs,它在使用依赖注入注入测试数据时变得更容易。
- Spring 的 web 框架是一个设计良好的 web MVC 框架,它为比如 Structs 或者其他工程上的或者不怎么受欢迎的 web 框架提供了一个很好的供替代的选择。MVC 模式导致应用程序的不同方面(输入逻辑,业务逻辑和UI逻辑)分离,同时提供这些元素之间的松散耦合。模型(Model)封装了应用程序数据,通常它们将由 POJO 类组成。视图(View)负责渲染模型数据,一般来说它生成客户端浏览器可以解释 HTML 输出。控制器(Controller)负责处理用户请求并构建适当的模型,并将其传递给视图进行渲染。
- Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
- 轻量级的 IOC 容器往往是轻量级的,例如,特别是当与 EJB 容器相比的时候。这有利于在内存和 CPU 资源有限的计算机上开发和部署应用程序。
- Spring 提供了一致的事务管理接口,可向下扩展到(使用一个单一的数据库,例如)本地事务并扩展到全局事务(例如,使用 JTA)。
1.3、依赖注入(DI)
Spring 最认同的技术是控制反转的**依赖注入(DI)**模式。控制反转(IoC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。
当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能的独立于其他的 Java 类来增加这些类可重用可能性,当进行单元测试时,可以使它们独立于其他类进行测试。依赖注入(或者有时被称为配线)有助于将这些类粘合在一起,并且在同一时间让它们保持独立。
到底什么是依赖注入?让我们将这两个词分开来看一看。这里将依赖关系部分转化为两个类之间的关联。例如,类 A 依赖于类 B。现在,让我们看一看第二部分,注入。所有这一切都意味着类 B 将通过 IoC 被注入到类 A 中。
依赖注入可以以向构造函数传递参数的方式发生,或者通过使用 setter 方法 post-construction。由于依赖注入是 Spring 框架的核心部分,所以我将在一个单独的章节中利用很好的例子去解释这一概念。
1.4、面向切面的程序设计(AOP)
Spring 框架的一个关键组件是面向切面的程序设计(AOP)框架。一个程序中跨越多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样常见的很好的关于方面的例子,比如日志记录、声明性事务、安全性,和缓存等等。
在 OOP 中模块化的关键单元是类,而在 AOP 中模块化的关键单元是方面。AOP 帮助你将横切关注点从它们所影响的对象中分离出来,然而依赖注入帮助你将你的应用程序对象从彼此中分离出来。
Spring 框架的 AOP 模块提
供了面向方面的程序设计实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦出来。使用源码级的元数据,可以用类似于 .Net 属性的方式合并行为信息到代码中。我将在一个独立的章节中讨论更多关于 Spring AOP 的概念。
2、Spring Ioc 容器
2.1、Spring ApplicationContext 容器
Application Context 是 BeanFactory 的子接口,也被称为 Spring 上下文。
Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。
ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
最常被使用的 ApplicationContext 接口实现:
- FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
- ClassPathXmlApplicationContext(推荐使用):该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
- WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
我们已经在 Spring Hello World Example章节中看到过 ClassPathXmlApplicationContext 容器,并且,在基于 spring 的 web 应用程序这个独立的章节中,我们讨论了很多关于 WebXmlApplicationContext。所以,接下来,让我们看一个关于 FileSystemXmlApplicationContext 的例子。
测试案例:
1、创建 maven 项目
- 创建项目后在pom.xml导入spring依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
</dependencies>
2、编写测试对象类
public class Person {
private String username;
private Integer age;
//...在这省略...(getter/setter/有参无参/tostring)
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- 默认通过空构造方法创建 bean -->
<bean id="person" class="com.quite.domain.Person"/>
</beans>
4、创建测试类
public class MainApp {
public static void main(String[] args) {
//加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//获取spring容器中的bean对象
Person person = (Person) context.getBean("person");
System.out.println(person);
}
}
5、运行测试
2.2、Spring bean 定义(常用几种)
注意:在配置文件加载后的时候,spring容器中管理的对象**(bean)**就已经初始化了。
方式一:默认通过无参构造创建bean
<bean id="person" class="com.quite.domain.Person"/>
方式二:通过右有参构造器创建bean
<bean class="com.quite.domain.Person" id="person2">
<constructor-arg name="age" value="18"/>
<constructor-arg name="username" value="quite"/>
</bean>
方式三:通过set方法创建bean
<bean class="com.quite.domain.Person" id="person3">
<property name="username" value="quite3"/>
<property name="age" value="18"/>
</bean>
测试:创建 MainApp.java
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person1 = (Person) context.getBean("person1");
Person person2 = (Person) context.getBean("person2");
Person person3 = (Person) context.getBean("person3");
System.out.println(person1);
System.out.println(person2);
System.out.println(person3);
}
}
2.3、Spring 配置
2.3.1、alias 别名
<!-- 配置 spring 容器中已存在 bean 的别名 -->
<alias name="person1" alias="hello"/>
测试:
public class MainApp01 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person hello = (Person) context.getBean("hello");
System.out.println(hello);
}
}
2.3.2、bean 配置
<!--
1、id:bean的唯一标识
2、class:指定创建bean对象
3、name:可以这个bean对象起多个名字(使用 , 分割或使用 空格 分割)
-->
<bean id="person5" class="com.quite.domain.Person" name="p1,p2 p3"/>
测试:
public class MainApp02 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person5 = (Person) context.getBean("person5");
Person p1 = (Person) context.getBean("p1");
Person p2 = (Person) context.getBean("p2");
Person p3 = (Person) context.getBean("p3");
System.out.println(person5);
System.out.println(p1);
System.out.println(p2);
System.out.println(p3);
}
}
2.3.3、import 引入配置
import作用:引入配置文件(可以引入多个),然后把这些配置文件整合成一个配置文件
1、主配置文件 ApplicationSpring.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-3.0.xsd">
<!-- 引入配置文件(把多个spring配置文件整合在一起) -->
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
</beans>
2、被引用的配置文件
beans1.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-3.0.xsd">
<!-- 默认通过空构造方法创建 bean -->
<bean id="person1" class="com.quite.domain.Person"/>
<!-- 通过有参构造创建 bean -->
<bean class="com.quite.domain.Person" id="person2">
<constructor-arg name="age" value="18"/>
<constructor-arg name="username" value="quite"/>
</bean>
<!-- setter方法创建 bean -->
<bean class="com.quite.domain.Person" id="person3">
<property name="username" value="quite3"/>
<property name="age" value="18"/>
</bean>
<!-- 配置 spring 容器中已存在 bean 的别名 -->
<alias name="person1" alias="hello"/>
<!--
1、id:bean的唯一标识
2、class:指定创建bean对象
3、name:可以这个bean对象起多个名字
-->
<bean id="person5" class="com.quite.domain.Person" name="p1,p2 p3"/>
</beans>
beans2.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-3.0.xsd">
<!-- 默认通过空构造方法创建 bean -->
<bean id="person6" class="com.quite.domain.Person"/>
</beans>
3、测试
public class MainApp03 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationSpring.xml");
Person person5 = (Person) context.getBean("person5");
Person p1 = (Person) context.getBean("p1");
Person p2 = (Person) context.getBean("p2");
Person p3 = (Person) context.getBean("p3");
Person person6 = (Person) context.getBean("person6");
System.out.println(person5);
System.out.println(p1);
System.out.println(p2);
System.out.println(p3);
System.out.println(person6);
}
}
总结:就是把多个spring配置文件整合在一起
注意:多个配置文件合并后可能会出现从名现象,spring会自动把从名的bean合并
2.4、依赖注入(DI)
理解:依赖注入
依赖:bean 对象的创建依赖与 spring 容器
注入:bean 对象的所有属性由 spring 容器注入
2.4.1、构造器注入
<!-- 默认通过空构造方法创建 bean 没有给属性注入值 -->
<bean id="person1" class="com.quite.domain.Person"/>
<!-- 通过有参构造创建 bean 并且给属性注入值 -->
<bean class="com.quite.domain.Person" id="person2">
<constructor-arg name="age" value="18"/>
<constructor-arg name="username" value="quite"/>
</bean>
测试:
public class MainApp04 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationSpring.xml");
Person person1 = (Person) context.getBean("person1");
Person person2 = (Person) context.getBean("person2");
System.out.println(person1);
System.out.println(person2);
}
}
2.4.2、setter方法注入(重点使用)
依赖注入:
依赖:bean对象的创建依赖spring容器
注入:bean对象的所有属性由spring容器注入
测试:
1、创建项目
自行创建maven项目!
2、创建测试对象类
Address.java
public class Address {
private String address;
//...get/set/tostring/有/无参构造
}
Student.java
public class Student {
private String name;
private String[] books;
private Address address;
private List<String> hobbys;
private Map<String, String> card;
private Set<String> games;
private String wife;
private Properties properties;
//...get/set/tostring/有/无参构造
}
3、创建配置文件
<?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-3.0.xsd">
<!-- 由spring容器创建Address对象(bean) -->
<bean id="address" class="com.quite.domain.Address">
<!-- 通过set方法注入属性 -->
<property name="address" value="广西"/>
</bean>
<bean id="student" class="com.quite.domain.Student">
<!-- 普通属性注入 -->
<property name="name" value="蔡蔡蔡"/>
<!-- bean对象注入 ref指定bean -->
<property name="address" ref="address"/>
<!-- 数组注入 -->
<property name="books">
<array>
<value>Spring 5 核心原理</value>
<value>Spring 实战</value>
<value>Spring Cloud 原理</value>
</array>
</property>
<!-- map注入 -->
<property name="card">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
<entry key="key3" value="value3"/>
</map>
</property>
<!-- set注入 -->
<property name="games">
<set>
<value>王者荣耀</value>
<value>刺激战场</value>
<value>天天飞车</value>
</set>
</property>
<!-- list注入 -->
<property name="hobbys">
<list>
<value>打蓝球</value>
<value>敲代码</value>
<value>吃</value>
</list>
</property>
<!-- null值注入 -->
<property name="wife">
<null/>
</property>
<!-- properties 注入-->
<property name="properties">
<props>
<prop key="邮箱">quite@qq.com</prop>
<prop key="QQ">481266251</prop>
<prop key="电话">1337725xxxx</prop>
</props>
</property>
</bean>
</beans>
4、测试
1、创建MainApp.java
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
}
}
总结:依赖注入就是bean对象的创建依赖于spring容器,通过setter方法注入属性值或通过构造方法注属性值。
2.4.3、p命名 和 c命名注入
在配置文件头部添加约束:
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- p命名空间注入,直接注入属性值:property -->
<bean name="user" class="com.quite.domain.User" p:username="88888" p:password="admin"/>
<!-- c命名空间注入,通过构造器注入:construct-args -->
<bean name="user1" class="com.quite.domain.User" c:username="admin" c:password="admin"/>
</beans>
测试:
public class MainApp1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans2.xml");
User user = (User) context.getBean("user");
User user1 = (User) context.getBean("user1");
System.out.println(user);
System.out.println(user1);
}
}
注意:使用 p 或 c 命名空间前,需要导入xml约束。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<!-- p命名空间约束 -->
xmlns:p="http://www.springframework.org/schema/p"
<!-- c命名空间约束 -->
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
</beans>
2.5、Spring Bean 作用域
当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton。
Spring 框架支持以下五个作用域,分别为 singleton、prototype、request、session 和 global session,5种作用域说明如下所示,
注意:如果你使用 web-aware ApplicationContext 时,其中三个是可用的。
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值(重点) |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()(重点) |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境 |
global-session | 一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境 |
本章将讨论前两个范围,当我们将讨论有关 web-aware Spring ApplicationContext 时,其余三个将被讨论。
2.5.1、singleton(默认的bean作用域)
理解:singleton 作用域每次返回都是同一个bean对象
<!--介绍两个常用作用域:
1、singleton:每次从容器中取出的Bean实例是同一个。
2、prototype:每次从容中取出的Bean都是一个新的Bean实例
-->
<bean id="user" class="com.quite.User" scope="prototype">
<property name="name" value="蔡蔡蔡"/>
<property name="age" value="18"/>
</bean>
2、创建测试Bean对象类(User.java)
public class User
{
private String name;
private Integer age;
//...getter setter toString 有参无参构造
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--介绍两个常用作用域
1、singleton:每次从容器中取出的Bean实例是同一个。
2、prototype:每次从容中取出的Bean都是一个新的Bean实例
-->
<bean id="user" class="com.quite.User" scope="prototype">
<property name="name" value="蔡蔡蔡"/>
<property name="age" value="18"/>
</bean>
</beans>
4、MainApp.java
public class MainApp {
public static void main(String[] args)
{
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user", User.class);
User user1 = context.getBean("user", User.class);
//作用域为 singleton 时,每次从容其中取出的都是同一个Bean实例。
System.out.println(user == user1);
//作用域为 prototype 时,每次从容器中取出的Bean都是一个新的bean实例
System.out.println(user == user1);
}
}
3、自动装配Bean
这种模式由属性名称指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 auto-wire 属性设置为 byName。然后,它尝试将它的属性与配置文件中定义为相同名称的 beans 进行匹配和连接。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。
3.1、byName
byName:按照容器中的 bean 实例 id 名注入属性值,通过 setter() 方法注入对象属性值
<!--自动装配(自动注入属性值)
autowire: 选择自动装配类型
-->
<bean id="person" class="com.quite.Person" autowire="byName">
<property name="username" value="hello"/>
</bean>
3.2、byType
byType:按属性类型装配,对应自己对象属性类型相同的 bean 注入
注意:如果Spring容器中有多个同类型的bean实例的情况使用byType注入属性值会报异常。
<bean id="person" class="com.quite.Person" autowire="byType">
<property name="username" value="hello"/>
</bean>
1、创建项目
2、编写测试类
Dog.java
public class Dog
{
public void ectShit()
{
System.out.println("狗在吃屎");
}
}
Shit.java
public class Shit
{
public void ectShit()
{
System.out.println("谁在吃屎");
}
}
Person.java
public class Person {
private String username;
private Dog dog;
private Shit shit;
//...getter/setter toString 有参/无参
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="dog" class="com.quite.Dog"/>
<bean id="shit" class="com.quite.Shit"/>
<!--自动装配(自动注入属性值):autowire=
byName:按照容器中的Bean 唯一名注入属性值
byType:按照容器中的Bean 类型注入属性值(如果容器中有多个同类型的Bean就会报异常)
-->
<bean id="person" class="com.quite.Person" autowire="byType">
<!-- 通过sett方法注入属性值 -->
<property name="username" value="hello"/>
</bean>
</beans>
4、MainApp.java
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean("person", Person.class);
person.getDog().ectShit();
person.getShit().ectShit();
System.out.println(person.getUsername());
}
}
总结:
byName:从容器中找到对应的bena id(加入容器对象的id名),通过sett方法注入对象属性值。
byType:从容器中找到同一个类型的Bean实例注入属性值,如果容器中有多个同类型属性就会报异常,在这种情况下我们应该使用byName的方式注入。
3.3、注解自动装配
使用之前配置约束
<?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-3.0.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
">
</beans>
3.3.1、开启注解支持(spring注解支持)
<!-- 开启bean注解支持 -->
<context:annotation-config/>
3.3.2、@Autowired
作用:按 byType 类型注入属性
用处:用在属性上或者setter方法上面
public class Person
{
private String username;
@Autowired
private Dog dog;
@Autowired
private Shit shit;
//......getter setter...
}
注意:如果容器中有多种同类型的bean实例,可以使用 @Qualifier(“name”) + @Autowired配合使用!
例子:
1、容器中有两个同类型的bean实例
<bean id="dog2" class="com.quite.Dog"/>
<bean id="dog1" class="com.quite.Dog"/>
2、通过 @Autowired+@Qualifier(“beanid”),注入指定的bean对象!
@Autowired
@Qualifier("dog1")
private Dog dog;
3.3.3、@Resource
作用:按 byName 类型注入属性(按beanId注入)
@Resource
private Dog dog;
例子:
1、容器中有两个同类型的bean实例
<bean id="dog2" class="com.quite.Dog"/>
<bean id="dog1" class="com.quite.Dog"/>
2、使用
@Resource
private Dog dog1; //根据 bean 名字注入 dog1注入容器中的dog1实例
4、纯注解实现Spring配置
理解:不需要在使用beans.xml配置文件,使用Java替代。
测试:
1、创建项目
2、创建测试类
/**
* @Component 把这个类注册到spring容器中(spring容器帮我们创建这个类对象),默认名字类名小写
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/14 22:10
*/
@Component
public class User
{
private String username;
//...getter/setter...
}
3、编写SpringConfig.java配置类,替换beans.xml
/**
* @Configuration 表示这是一个配置类,相当于beans.xml
* @ComponentScan("com.quite") 扫描指定路径下的包的@Component注解,该注解会把对象注入spring容器。
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/14 22:11
*/
@Configuration
@ComponentScan("com.quite")
public class SpringConfig
{
/**
* @Bean 把该对象加入spring容器类似@Component注解
* 其实就是:beans.xml配置文件中的普通bean注册到spring容器
* <bean id="getUser" class="com.quite.User"/>
*
* id 等于 getUser
* class = 返回值(return new User();)
*
* @return
*/
@Bean
public User getUser()
{
return new User();
}
}
4、测试
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/14 22:15
*/
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = context.getBean("user", User.class);
System.out.println(user);
}
}
总结:@Configuration 替换 beans.xml, @Component 替换 “bean” 标签!
5、代理模式
1、什么是代理?
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
5.1、静态代理
**静态代理模式:**静态代理说白了就是在程序运行前就已经存在代理类的字节码文件,代理类和原始类的关系在运行前就已经确定。
实例:
1、创建代理类和委托类统一实现接口
/**
* 这是代理类和委托类统一要实现的接口
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 10:40
*/
public interface UserService
{
void addUser();
void updateUser();
void deleteUser();
void queryUser();
}
2、创建委托类实现接口(被代理的类)
/**
* 委托类(被代理的对象)
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 10:40
*/
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void updateUser() {
System.out.println("更新用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
@Override
public void queryUser() {
System.out.println("查询用户");
}
}
3、创建代理类
/**
* 代理类
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 10:43
*/
public class UserServiceImplProxy implements UserService
{
private UserService userService;
/**
* 通过setter方法注入传入委托对象
* @param userService
*/
public void setUserService(UserService userService)
{
this.userService = userService;
}
/**
* 通过构造方法注入传入委托对象
*/
public UserServiceImplProxy(UserService userService)
{
this.userService = userService;
}
public UserServiceImplProxy()
{
}
@Override
public void addUser()
{
log("add日志");
userService.addUser();
}
@Override
public void updateUser()
{
log("update日志");
userService.updateUser();
}
@Override
public void deleteUser()
{
log("delete日志");
userService.deleteUser();
}
@Override
public void queryUser()
{
log("query日志");
userService.queryUser();
}
//每个方法调用前执行的日志方法
public void log(String msg)
{
System.out.println("记录日志:"+msg);
}
}
4、客户端测试类
/**
* 静态代理:
* 例如:你要买房找不到房东?这时候就需要找房子出租的中介公司,然后中介公司帮你租房子。
* 客户端调用通过代理调用委托类(你要买房 -> 中介(代理) -> 房东)
*
* 缺点:
* 1、代理使客户端不需要知道实现类(委托对象)是什么,怎么做的,而客户端只需知道代理(中介)即可(解耦合)
* 2、代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
* 3、代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,【静态代理】在程序规模稍大时就无法胜任了。
*
* 作用理解:
* 1、代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,
* 在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。
* 2、但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)
* 3、即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 10:45
*/
public class Client
{
public static void main(String[] args)
{
//代理对象帮我们操作委托对象
UserService proxy = new UserServiceImplProxy(new UserServiceImpl());
//通过代理调用委托对象方法
proxy.addUser();
}
}
5.2、动态代理
动态代理模式:动态代理类的源码是在程序运行期间通过JVM反射等机制动态生成,代理类和委托类的关系是运行时才确定的。
实例:
1、创建代理类和委托类统一实现接口
/**
* 这是代理类和委托类统一要实现的接口
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 10:40
*/
public interface UserService
{
void addUser();
void updateUser();
void deleteUser();
void queryUser();
}
2、创建委托类实现接口(被代理的类)
/**
* 委托类(被代理的对象)
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 10:40
*/
public class UserServiceImpl implements UserService
{
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void updateUser() {
System.out.println("更新用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
@Override
public void queryUser() {
System.out.println("查询用户");
}
}
3、创建动态代理类
/**
* 动态代理对象
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 12:46
*/
public class DynamicProxyHandler implements InvocationHandler
{
/* 目标对象(委托类)*/
private Object target;
/**
* 我们可以通过DynamicProxyHandler代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,
* 那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,
* 比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。
*
* @param proxy 代理
* @param method 调用方法
* @param args 参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Object result=null; //返回结果
try{
/*原对象方法调用前处理日志信息*/
System.out.println("---调用目标方法前记录日志---");
/* target(委托代理对象),args(目标方法的参数) */
result=method.invoke(target, args); /*调用目标方法*/
/*原对象方法调用后处理日志信息*/
System.out.println("---调用目标方法后记录日志---");
}catch(Exception e){
e.printStackTrace();
/* 异常日志打印*/
System.out.println("---error---");
throw e;
}
return result;
}
/**
* 动态生成的代理实例
* @param targetObject 委托代理对象(UserServiceImpl)
* @return
*/
public Object newProxyInstance(Object targetObject)
{
this.target = targetObject;
/**
* 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
* 第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
* 第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
* 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
* 根据传入的目标返回一个代理对象
*/
return Proxy.newProxyInstance(
targetObject.getClass().getClassLoader(), //获取委托代理对象的类加载器
targetObject.getClass().getInterfaces(), //获取委托对象的实现接口
this //得到InvocationHandler接口的子类实现(DynamicProxyHandler)
);
}
}
4、创建客户端测试
/**
* 动态代理:
* 1、根据静态代理的使用,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,
* 所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理。
*
* 2、在上静态代理中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。
* 而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象
*
* 3、在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持。
*
* 4、java.lang.reflect.InvocationHandler接口的定义:
* Object proxy:被代理的对象
* Method method:要调用的方法
* Object[] args:方法调用时所需要参数
* public interface InvocationHandler {
* public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
* }
*
* 5、java.lang.reflect.Proxy类的定义:
* CLassLoader loader:类的加载器
* Class<?> interfaces:得到全部的接口
* InvocationHandler h:得到InvocationHandler接口的子类的实例
* public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
*
* 总结:纵观静态代理与动态代理,它们都能实现相同的功能,
* 而我们看从静态代理到动态代理的这个过程,我们会发现其实动态代理只是对类做了进一步抽象和封装,
* 使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念,其中还有AOP的身影,
* 这也提供给我们对类抽象的一种参考。
* 关于动态代理与AOP的关系,个人觉得AOP是一种思想,而动态代理是一种AOP思想的实现!
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 12:38
*/
public class Client {
public static void main(String[] args){
DynamicProxyHandler handler = new DynamicProxyHandler();
UserService userService = (UserService) handler.newProxyInstance(new UserServiceImpl());
userService.addUser();
userService.updateUser();
}
}
总结: 静态代理与动态代理,它们都能实现相同的功能,
- 而我们看从静态代理到动态代理的这个过程,我们会发现其实动态代理只是对类做了进一步抽象和封装,使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念,其中还有AOP的身影。
- 关于动态代理与AOP的关系,个人觉得AOP是一种思想,而动态代理是一种AOP思想的实现!
6、面向切面编程(AOP)
Spring 框架的一个关键组件是面向切面的程序设计(AOP)框架。一个程序中跨越多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样常见的很好的关于方面的例子,比如日志记录、声明性事务、安全性,和缓存等等。
作用:
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
Aop基本术语:
1、Advice 通知
AOP 的主要作用就是在不侵入原有程序的基础上实现对原有功能的增强, 而增强的方式就是添加通知,就是额外增强一个方法。按照不同的方式通知又分为前置、后置、环绕、异常、带有返回值。
2、JointPoint 连接点
在上述通知的描述中我们知道,AOP增强就是为原有方法在不侵入的情况下额外添加了一个新的功能,而新功能和原有方法之间是如何联系到一起的呢?
// 通过注解来把增强方法和原有方法链接到一起,实现了增强的功能。
@Around("execution(* com.quite.service.UserServiceImpl.*(..))")
3、Pointcut 切入点
在上述连接点的叙述中,我们知道了AOP 是如何实现都原有功能增强的, 但是通过连接点,增强方法是如何增强原有方法的呢? 这里就需要切入点来实现对原有方法的切入。
/* 切入点*/
@Pointcut("execution(* com.quite.service.impl.UserServiceImpl.*(..))")
public void result() {}
4、AOP通知方式
SpringAop同有5中通知类型:
1、@Before 前置通知
通知方法会在目标方法调用之前执行
2、@After 后置通知
通知方法会在目标方法调用之前执行
3、@AfterReturning 后置通知,返回方法结果
通知方法可以获取到目标方法返回结果
4、@AfterThorwing 异常通知
通知方法会在目标方法出现异常的时候执行
5、@Around 环绕通知
通知方法会将目标方法封装起来,做增强处理(处理完返回结果)
具体看代码即可:-- >
6.1、XML配置实现AOP
使用Aop需要导入依赖:
<!-- aop依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
1、创建项目
2、创建UserService接口
/**
* 动态代理:代理的是接口
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 14:54
*/
public interface UserService
{
void addUser();
void deleteUser();
void updateUser();
void queryUser();
}
3、创建UserServiceImpl实现类
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 14:56
*/
public class UserServiceImpl implements UserService
{
@Override
public void addUser()
{
System.out.println("add User...");
}
@Override
public void deleteUser()
{
System.out.println("delete User...");
}
@Override
public void updateUser()
{
System.out.println("update User...");
}
@Override
public void queryUser()
{
System.out.println("query User...");
}
}
4、创建LogInfo切面类
/**
* 切面类
* 记录日志信息
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 14:59
*/
public class LogInfo
{
/* 切入点*/
@Pointcut("execution(* com.quite.service.impl.UserServiceImpl.*(..))")
public void result() {}
/* 前置处理*/
public void before()
{
System.out.println("--------前置处理 记录日志-------");
}
/* 后置处理*/
public void after()
{
System.out.println("--------后置处理 记录日志-------");
}
/* 后置处理*/
public void afterReturning()
{
System.out.println("--------后置处理返回(可以获取方法返回结果进行返回)-------");
}
/*
* 环绕通知(增强通知,将目标方法封装起来)
* ProceedingJoinPoint point 过程切入点,增强方法在此实现对被增强方法的功能增强。通过切入点我们可以得到很多东西
* */
public void around(ProceedingJoinPoint point)
{
try {
System.out.println("--------环绕通知 前置处理 记录日志-------");
System.out.println(point.getSignature().getName());//获取执行的方法名
point.proceed(); //真实执行方法
System.out.println("--------环绕通知 后置处理 记录日志-------");
} catch (Throwable throwable)
{
throwable.printStackTrace();
System.out.println("--------环绕通知 异常处理 记录日志-------");
}
}
}
5、创建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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean -->
<bean id="userService" class="com.quite.service.impl.UserServiceImpl"/>
<!-- 切面类实例 -->
<bean id="logInfo" class="com.quite.log.LogInfo"/>
<!-- Aop配置
设置expose-proxy属性为true暴露代理(作用:例如有两个目标方法)。
-->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="qud" expression="execution(* com.quite.service.impl.UserServiceImpl.*(..))"/>
<!-- 切面
ref:指定切面类
-->
<aop:aspect ref="logInfo">
<!--
aop:before:前置通知(切入方法执行前执行)
aop:after:前置通知(切入方法执行后执行)
aop:around:环绕通知(围绕着方法执行)
-->
<aop:before method="before" pointcut-ref="qud"/>
<aop:after method="before" pointcut-ref="qud"/>
<aop:around method="around" pointcut-ref="qud"/>
<aop:after-returning method="afterReturning" pointcut-ref="qud"/>
</aop:aspect>
</aop:config>
</beans>
6、创建测试类
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 15:08
*/
public class MainAop
{
public static void main(String[] args)
{
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
/*
* 注意:动态代理是代理接口实现,所以必须返回被代理对象实现的接口。
* */
UserService service = context.getBean("userService", UserService.class);
service.addUser();
}
}
注意:动态代理是代理接口实现,所以必须返回被代理对象实现的接口。
6.2、纯注解实现AOP
aop依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
1、创建项目
2、配置类
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 20:07
*/
@Configuration //相当于 ApplicationContext.xml配置文件
@ComponentScan("com.quite") //扫描指定路径下容器中所有bean实例
/**
* exposeProxy = true:设置expose-proxy属性为true暴露代理。
* proxyTargetClass = true:设置为true表示使用静态代理(默认 false)
*/
@EnableAspectJAutoProxy //开启Aop注解
public class SpringConfig
{
}
3、UserService \ UserServiceImpl
/**
* 动态代理是通过接口实现
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 14:54
*/
public interface UserService
{
void addUser();
void deleteUser();
void updateUser();
void queryUser();
}
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 14:56
*/
@Service //把该对象注册到springIoc容器中
public class UserServiceImpl implements UserService
{
@Override
public void addUser()
{
System.out.println("add User...");
}
@Override
public void deleteUser()
{
System.out.println("delete User...");
}
@Override
public void updateUser()
{
System.out.println("update User...");
}
@Override
public void queryUser()
{
System.out.println("query User...");
}
}
4、LogInfo
/**
* 切面类
* 记录日志信息
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 14:59
*/
@Component
@Aspect //表示这是一个切面
public class LogInfo
{
/* 切入点*/
@Pointcut("execution(* com.quite.service.impl.UserServiceImpl.*(..))")
public void result() {}
/* 前置处理*/
@Before("result()")
public void before()
{
System.out.println("--------前置处理 记录日志-------");
}
/* 后置处理*/
@After("result()")
public void after()
{
System.out.println("--------后置处理 记录日志-------");
}
/* 返回结果处理*/
@AfterReturning("result()")
public void afterReturning()
{
System.out.println("--------目标方法返回结果执行-------");
}
/*
* 环绕通知(增强通知,将目标方法封装起来)
* ProceedingJoinPoint point 过程切入点,增强方法在此实现对被增强方法的功能增强。通过切入点我们可以得到很多东西
* */
//@Around("result()")
public void around(ProceedingJoinPoint point)
{
try
{
System.out.println("--------环绕通知 前置处理 记录日志-------");
System.out.println(point.getSignature().getName());//获取执行的方法名
point.proceed(); //真实执行方法
System.out.println("--------环绕通知 后置处理 记录日志-------");
} catch (Throwable throwable)
{
throwable.printStackTrace();
System.out.println("--------环绕通知 异常处理 记录日志-------");
}
}
}
5、MainAop测试
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/2/15 15:08
*/
public class MainAop
{
public static void main(String[] args)
{
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
/*
* 注意:动态代理是代理接口实现,所以必须返回被代理对象实现的接口。
* */
UserService service = context.getBean("userServiceImpl", UserService.class);
service.addUser();
}
}
注意:还没有完全理解,以后总结(基础)
7、Spring事务管理
1、开启事务管理器
2、使用事务(@transaction)
@transaction //开启事务,注解可以放在方法上或者类上
事务:要么成功,要么失败!