1. Spring 的优点
- Spring 是一个开源的、免费的容器(框架)
- Spring 是一个轻量级的、非侵入式的框架
- 控制翻转(IOC),面向切面编程(AOP)
- 支持事务的处理,对框架整合的支持
2. maven 依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.2</version>
</dependency>
3. 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。
4. 控制翻转
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
4.1 Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用Spring来创建对象,在Spring这些都称为Bean -->
<bean id="hello" class="com.ice.pojo.Hello" name="hello2 hello3">
<property name="str" value="Spring"/>
</bean>
</beans>
- id 属性是 bean 的唯一表示,遵从小驼峰命名法
- name 属性表示该 bean 的别名,可以有多个别名,分隔符可以是空格、逗号、分号,允许混合同时使用
但是,在实际定义bean的地方指定所有别名并不总是足够的。有时需要为在别处定义的bean引入别名。这在大型系统中通常是这种情况,在大型系统中,配置在每个子系统之间分配,每个子系统都有自己的对象定义集。在基于XML的配置元数据中,可以使用
<alias/>
元素来完成此任务。<alias name="hello" alias="hello4"/>
4.2 实例化 Bean 的方式
4.2.1 构造函数实例化
当通过构造方法创建一个bean时,所有普通类都可以被Spring使用并兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定bean类就足够了。但是,根据用于该特定bean的IoC的类型,可能需要一个默认(空)构造函数。
Spring IoC容器几乎可以管理您要管理的任何类。它不仅限于管理真正的JavaBean。大多数Spring用户更喜欢实际的JavaBean,它们仅具有默认的(无参数)构造函数,并具有根据容器中的属性建模的适当的setter和getter。
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
默认的 bean 就是构造函数来实例化的
4.2.2 静态工厂方法实例化
在定义使用静态工厂方法创建的 bean 时,使用class
属性指定包含static
工厂方法的类,并使用命名factory-method
为属性的属性来指定工厂方法本身的名称。您应该能够调用此方法(带有可选参数,如稍后所述)并返回一个活动对象,该对象随后将被视为已通过构造函数创建。
以下bean定义指定通过调用工厂方法来创建bean。该定义不指定返回对象的类型(类),而仅指定包含工厂方法的类。在此示例中,该createInstance()
方法必须是静态方法。
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
以下示例显示了可与前面的bean定义一起使用的类:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {
}
public static ClientService createInstance() {
return clientService;
}
}
4.2.3 实例工厂方法实例化
类似于通过静态工厂方法进行实例化,使用实例工厂方法进行实例化会从容器中调用现有bean的非静态方法来创建新bean。要使用此机制,请将class
属性留空,并在factory-bean
属性中指定当前(或父容器或祖先容器)中包含要创建该对象的实例方法的Bean的名称。使用factory-method
属性设置工厂方法本身的名称。
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
以下示例显示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类也可以包含一个以上的工厂方法,如以下示例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
<bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>
以下示例显示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
4.3 依赖注入
4.3.1 构造函数注入
-
引用已有的 bean 注入
package x.y; public class ThingOne { public ThingOne(ThingTwo thingTwo, ThingThree thingThree) { // ... } }
<beans> <bean id="beanOne" class="x.y.ThingOne"> <constructor-arg ref="beanTwo"/> <constructor-arg ref="beanThree"/> </bean> <bean id="beanTwo" class="x.y.ThingTwo"/> <bean id="beanThree" class="x.y.ThingThree"/> </beans>
-
构造函数参数类型匹配注入
package examples; public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
-
构造函数参数索引注入
可以使用该
index
属性来明确指定构造函数参数的索引,如以下示例所示:<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
- 除了解决多个简单值的歧义性外,指定索引还可以解决构造函数具有两个相同类型参数的歧义性
- 索引从 0 0 0开始
-
构造函数参数名称注入
还可以使用构造函数参数名称来消除歧义,如以下示例所示:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean>
请记住,要立即使用该功能,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名称。如果您不能或不想使用debug标志编译代码,则可以使用
@ConstructorProperties
JDK注释显式命名构造函数参数。然后,样本类必须如下所示:package examples; public class ExampleBean { // Fields omitted @ConstructorProperties({ "years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
4.3.2 setter 注入
通过调用无参数构造函数或无参数static
工厂方法实例化Bean后,容器在Bean上调用setter方法来实现基于Setter的DI 。
4.3.2.1 简单类型
<property/>
元素的value
属性将属性或构造函数参数指定为人类可读的字符串表示形式。Spring的转换服务用于将这些值从字符串转换为属性或参数的实际类型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</bean>
4.3.2.2 idref 元素
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
前面的bean定义片段(在运行时)与下面的片段完全等效:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一种形式优于第二种形式,因为使用
idref
标记可以使容器在部署时验证所引用的命名Bean实际上是否存在。在第二个变体中,不对传递给BeantargetName
属性的值进行验证client
。拼写错误仅在client
实际实例化bean时才发现(可能会导致致命的结果)。
4.3.2.3 对其他Bean的引用
通过<ref/>
标记的bean属性指定目标bean是最通用的形式,它允许创建对同一容器或父容器中的任何bean的引用,而不管它是否在同一个XML文件中。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的一个值相同。下面的例子演示了如何使用ref元素:
<ref bean="someBean"/>
通过parent属性指定目标bean,将创建对当前容器的父容器中的bean的引用。父属性的值可以与目标bean的id属性或目标bean的name属性中的一个值相同。目标bean必须位于当前bean的父容器中。当您有一个容器层次结构,并且希望使用与父bean同名的代理将现有bean包装在父容器中时,您应该使用这个bean引用变量。下面的两个清单展示了如何使用parent属性:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<!-- bean name is the same as the parent bean -->
<bean id="accountService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
4.3.2.4 内部 bean
<property/>
或<constructor-arg/>
元素中的<bean/>
元素定义了一个内部bean,如下面的示例所示:
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
- 内部bean定义不需要已定义的ID或名称。如果指定了该值,则容器不会使用该值作为标识符。
- 容器在创建时也会忽略范围标记,因为内部bean总是匿名的,并且总是与外部bean一起创建。
- 不能独立访问内部bean,也不能将它们注入到协作bean中。
- 内部bean通常只是共享它们所包含的bean的范围。
4.3.2.5 Collections
<list/>
、<set/>
、<map/>
、<props/>
元素分别设置了Java集合类型 List
、Set
、Map
和 Properties
的属性和参数。下面的示例演示如何使用它们:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
<prop key="development">[email protected]</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
<property name="someNull">
<null />
</property>
</bean>
map 的键和值、set的值可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
4.3.2.6 Collections 合并
Spring容器还支持合并集合。一个应用开发者可以定义一个父元素<list/>
, <map/>
, <set/>
或<props/>
,并让子元素<list/>
,<map/>
,<set/>
或<props/>
继承和覆盖父元素集合中的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合元素覆盖父集合中指定的值。
下面的例子演示了集合的合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<beans>
其结果如下所示:
[email protected]
[email protected]
[email protected]
4.3.2.7 null 和字符串控制
Spring将属性之类的空参数视为空字符串。以下基于xml的配置元数据片段将email属性设置为空字符串值(")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
<null/>
元素的作用是:处理null
值。
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
4.3.2.8 p 命名空间注入
下面的示例显示了两个XML片段(第一个使用标准XML格式,第二个使用p-namespace),它们解析为相同的结果:
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="[email protected]"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="[email protected]"/>
</beans>
该示例显示了 bean 定义中的 p-namespace 中的一个名为 email 的属性。这告诉 Spring 包含一个属性声明。如前所述,p-namespace 没有模式定义,因此可以将属性的名称设置为属性名。
下一个示例包含了另外两个 bean 定义,它们都引用了另一个 bean:
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
4.3.2.9 c 命名空间注入
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="[email protected]"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="[email protected]"/>
</beans>
4.4 bean 的作用域
4.5 bean 的自动装配
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻找,并自动给bean装配属性
在Spring中有三种装配的方式
- 在xml中显式的配置
- 在java中显式配置
- 隐式的自动装配bean【重要】
4.5.1 byName 自动装配
该模式表示根据Property的Name自动装配,如果一个bean的name,和另一个bean中的Property的name相同,则自动装配这个bean到Property中。
当一个bean节点带有 autowire byName的属性时,将查找其类中所有的set方法名,获得将set去掉并且首字母小写的字符串,然后去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。
<bean id="cat" class="com.spring.auto.autowire.Cat"></bean>
<bean id="dog" class="com.spring.auto.autowire.Dog"></bean>
<bean id="test" class="com.spring.auto.autowire.Person" autowire="byName">
<property name="say" value="测试"/>
</bean>
如果将如果将bean id="cat’的值改成bean id=“catname”,执行时报空指针Exception in thread “main” java.lang.NullPointerException at com.spring.auto.autowire.Test.main(Test.java:11)。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
4.5.2 byType 自动装配
该模式表示根据Property的数据类型(Type)自动装配,Spring会总动寻找与属性类型相同的bean,若一个bean的数据类型,兼容另一个bean中Property的数据类型,则自动装配。
注意:使用byType首先需要保证同一类型的对象,在spring容器中唯一,若不唯一会报不唯一的异常。
<bean id="cat" class="com.spring.auto.autowire.Cat"></bean>
<bean id="dog" class="com.spring.auto.autowire.Dog"></bean>
<bean id="test" class="com.spring.auto.autowire.Person" autowire="byType">
<property name="say" value="测试"/>
</bean>
4.5.3 注解实现自动装配
4.5.3.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"
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/>
<!-- 指定要扫描的包,这个包下的注解就会生效 -->
<context:component-scan base-package="com.ice.pojo"/>
</beans>
4.5.3.2 @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;
}
}
使用
@Autowired
我们可以不用编写setter方法了,前提是你这个自动装配的属性在IoC(Spring)容器中存在,且符合 byType
@Autowired(required=false)
说明:false,对象可以为null;true,对象必须存对象,不能为null。//如果允许对象为null,设置required = false,默认为true @Autowired(required = false) private Cat cat;
4.5.3.3 @Qualifier
@Autowired
是根据类型自动装配的,加上@Qualifier
则可以根据byName的方式自动装配@Qualifier
不能单独使用。
<bean id="dog1" class="com.kuang.pojo.Dog"/>
<bean id="dog2" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
4.5.3.4 @Resource
@Resource
如有指定的name属性,先按该属性进行byName方式查找装配;- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String str;
}
4.6 使用注解开发
4.6.1 bean 的实现
我们之前都是使用 bean 的标签进行 bean 注入,但是实际开发中,我们一般都会使用注解!
-
配置扫描哪些包下的注解
<!--指定注解扫描包--> <context:component-scan base-package="com.kuang.pojo"/>
-
在指定包下编写类,增加注解
@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { public String name = "秦疆"; }
-
测试
@Test public void test(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) applicationContext.getBean("user"); System.out.println(user.name); }
4.6.2 属性注入
-
可以不用提供set方法,直接在直接名上添加
@value("值")
@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { @Value("秦疆") // 相当于配置文件中 <property name="name" value="秦疆"/> public String name; }
-
如果提供了set方法,在set方法上添加
@value("值")
@Component("user") public class User { public String name; @Value("秦疆") public void setName(String name) { this.name = name; } }
4.6.3 衍生的注解
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
@Controller
:web层@Service
:service层@Repository
:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
4.6.4 自动装配注解
见 4.5.3
4.6.5 作用域
@scope
singleton
:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。prototype
:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
@Value("秦疆")
public String name;
}
4.6.6 XML 与注解比较
xml 与注解整合开发 :推荐最佳实践
- xml 管理 Bean
- 注解完成属性注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>
作用:
- 进行注解驱动注册,从而使注解生效
- 用于激活那些已经在 Spring 容器里注册过的 bean 上面的注解,也就是显式的向 Spring 注册
- 如果不扫描包,就需要手动配置 bean
- 如果不加注解驱动,则注入的值为 null!
4.7 基于 Java 类进行配置
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
-
编写一个实体类,Dog
@Component //将这个类标注为Spring的一个组件,放到容器中! public class Dog { public String name = "dog"; }
-
新建一个config配置包,编写一个MyConfig配置类
@Configuration //代表这是一个配置类 public class MyConfig { @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id! public Dog dog(){ return new Dog(); // 返回的就是要注入到IoC容器的bean对象 } }
-
测试
@Test public void test(){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); Dog dog = (Dog) applicationContext.getBean("dog"); System.out.println(dog.name); }
导入其他配置如何做呢?
-
我们再编写一个配置类
@Configuration //代表这是一个配置类 public class MyConfig2 { }
-
在之前的配置类中我们来选择导入这个配置类
@Configuration @Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 import 标签 public class MyConfig { @Bean public Dog dog(){ return new Dog(); } }
5. 代理模式
代理模式是SpringAOP的底层
5.1 静态代理
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理着你是角色,代理真实角色后,我们一般会做一些复数操作
- 客户:访问代理对象的人
以租房举例:
【抽象角色】
package com.ice.demo01;
public interface Rent {
public void rent();
}
【真实角色】
package com.ice.demo01;
// 房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要租房子了");
}
}
【代理角色】
package com.ice.demo01;
// 房屋中介
public class Proxy implements Rent{
private Host host;
public Proxy(Host host) {
this.host = host;
}
public Proxy() {
}
public void seeHouse(){
System.out.println("中介带你看房");
}
public void fare(){
System.out.println("收中介费");
}
@Override
public void rent() {
host.rent();
}
}
【客户】
package com.ice.demo01;
public class Client {
public static void main(String[] args) {
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.seeHouse();
proxy.rent();
proxy.fare();
}
}
代理模式的优点:
- 可以使真实角色的操作哦更加纯粹,不用去关注一些公共的业务
- 公共业务就交给代理角色,实现了业务的分工
- 公共业务扩展的时候,方便集中管理
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍
5.2 动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的
- 动态代理分两大类:基于接口的动态代理 和 基于类的动态代理
- 基于接口:JDK 动态代理
- 基于类:cglib
- jav a字节码实现:javasist
需要了解两个类:Proxy
和 InvocationHandler
还是以租房为例:
【抽象角色】
package com.ice.demo012;
public interface Rent {
public void rent();
}
【真实角色】
package com.ice.demo02;
// 房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要租房子了");
}
}
【代理角色】
package com.ice.demo02;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
// 动态生成代理对象
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
@Override
// 处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理的本质,就是使用反射机制实现
Object result = method.invoke(rent, args);
return result;
}
}
【客户】
package com.ice.demo02;
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();
}
}
5.3 通用动态代理处理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyHandler implements InvocationHandler {
// 被动态代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 动态生成代理对象
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
// 处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// before call method do something
Object result = method.invoke(target, args);
// after call method do something
return result;
}
}
6. AOP
6.1 什么是AOP
AOP(Aspect Oriented Programming)意味面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
6.2 AOP 在 Spring 中的作用
提供声明式事务;允许用户自定义切面
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
6.3 使用 Spring 实现 AOP
需要导入一个新的包:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
6.3.1 使用 Spring API 实现 AOP
首先编写我们的业务接口和实现类
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
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 select() {
System.out.println("查找用户");
}
}
然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
public class Log implements MethodBeforeAdvice {
@Override
// method : 要执行的目标对象的方法
// objects : 被调用的方法的参数
// o : 目标对象
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
public class AfterLog implements AfterReturningAdvice {
@Override
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName() + "的" + method.getName() + "方法," + "返回值:" + returnValue);
}
}
最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns: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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册 bean -->
<bean id="userService" class="com.ice.service.UserServiceImpl"/>
<bean id="log" class="com.ice.log.Log"/>
<bean id="afterLog" class="com.ice.log.AfterLog"/>
<!-- 配置AOP:需要导入AOP的约束 -->
<aop:config>
<!-- 切入点:expression:表达式,execution(要执行的位置) -->
<aop:pointcut id="pointcut" expression="execution(* com.ice.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加 -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
结果:
com.ice.service.UserServiceImpl的add方法被执行了
增加用户
执行了com.ice.service.UserServiceImpl的add方法,返回值:null
6.3.2 自定义实现 AOP
public class DiyPointCut {
public void before(){
System.out.println("===before===");
}
public void after(){
System.out.println("===after===");
}
}
配置文件变为
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册 bean -->
<bean id="userService" class="com.ice.service.UserServiceImpl"/>
<bean id="log" class="com.ice.log.Log"/>
<bean id="afterLog" class="com.ice.log.AfterLog"/>
<bean id="diy" class="com.ice.diy.DiyPointCut"/>
<!-- 配置AOP:需要导入AOP的约束 -->
<aop:config>
<!-- 自定义切面,ref 要引用的类 -->
<aop:aspect ref="diy">
<!-- 切入点 -->
<aop:pointcut id="point" expression="execution(* com.ice.service.UserServiceImpl.*(..))"/>
<!-- 通知 -->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
6.3.3 使用注解实现 AOP
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect // 标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.ice.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("方法执行前");
}
@After("execution(* com.ice.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("方法执行前");
}
@Around("execution(* com.ice.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册 bean -->
<bean id="userService" class="com.ice.service.UserServiceImpl"/>
<bean id="log" class="com.ice.log.Log"/>
<bean id="afterLog" class="com.ice.log.AfterLog"/>
<bean id="annotationPointCut" class="com.ice.diy.AnnotationPointCut"/>
<aop:aspectj-autoproxy/>
</beans>
执行结果:
环绕前
方法执行前
增加用户
方法执行前
环绕后
注意执行顺序
7. 整合 Mybatis
7.1 导入相关 jar 包
- junit
- mybatis
- mysql
- spring全家桶
- aspectj
- mybatis-spring
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ice</groupId>
<artifactId>spring-study</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>ioc_01</module>
<module>hello-spring</module>
<module>proxy</module>
<module>spring-aop</module>
<module>spring-mybatis</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
7.2 环境准备
【User】
package com.ice.pojo;
import java.util.StringJoiner;
public class User {
private int id;
private String name;
private String pwd;
@Override
public String toString() {
return new StringJoiner(", ", User.class.getSimpleName() + "[", "]")
.add("id=" + id)
.add("name='" + name + "'")
.add("pwd='" + pwd + "'")
.toString();
}
}
【UserMapper】
package com.ice.mapper;
import com.ice.pojo.User;
import java.util.List;
public interface UserMapper {
public List<User> getUsers();
}
【UserMapper.xml】
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ice.mapper.UserMapper">
<select id="getUsers" resultType="User">
select * from user;
</select>
</mapper>
【UserMapperImpl】
package com.ice.mapper;
import com.ice.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
@Override
public List<User> getUsers() {
UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
return mapper.getUsers();
}
}
注意,整合后必须写实现类了,但是实现类里专注编码与数据库交互的逻辑
7.3 编写配置文件
【mybatis-config.xml】
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置-->
<configuration>
<typeAliases>
<package name="com.ice.pojo"/>
</typeAliases>
</configuration>
【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 id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 绑定 Mybatis 配置文件(可选,两个配置文件必须选一个配置) -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/ice/mapper/*.xml"/>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean id="userMapper" class="com.ice.mapper.UserMapperImpl">
<property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
</bean>
</beans>
7.4 测试
import com.ice.mapper.UserMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class MyTest {
public static void main(String[] args) throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
userMapper.getUsers().forEach(System.out::println);
}
}
7.5 简化操作
【UserMapperImpl2】
package com.ice.mapper;
import com.ice.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> getUsers() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.getUsers();
}
}
【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 id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 绑定 Mybatis 配置文件(可选,两个配置文件必须选一个配置) -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/ice/mapper/*.xml"/>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean id="userMapper2" class="com.ice.mapper.UserMapperImpl2">
<property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
</bean>
</beans>
8. 声明式事务
一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager
来实现事务管理。
一旦配置好了 Spring 的事务管理器,你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional
注解和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession
对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。
事务配置好了以后,MyBatis-Spring 将会透明地管理事务。这样在你的 DAO 类中就不需要额外的代码了。
一般使用声明式事务(AOP风格)
标准配置
【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 省略 -->
<!-- 配置声明式事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
<!-- 结合AOP实现事务的织入 -->
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 给哪些方法配置事务,可以用*表示所有方法 -->
<!-- propagation 是传播特性,默认 required 使用事务 -->
<tx:method name="update" propagation="REQUIRED"/>
<!-- read-only 只读 -->
<tx:method name="getUsers" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切入 -->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.ice.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>