- Spring5
- {}里面可以是’字符串’,18(数字),5+2(表达式),false(Boolean),T(com.xiao.config.AppConfig).get()[调用静态方法]等……
- {T(com.xiao.config.AppConfig).getIntProp(‘info.properties’,’age’)}实现了取出配置值
Spring5
官方文档:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html
1.概述
Spring 是一个主流的 Java Web 开发框架,该框架是一个轻量级的应用框架。
以 IOC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,面向切面编程)为内核,使用基本的 JavaBean 完成以前只可能由 EJB 完成的工作,取代了 EJB 臃肿和低效的开发模式。
Spring 框架的主要优点具体如下。
- 方便解耦,简化开发
Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。
- 方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持。
- 降低 Java EE API 的使用难度
Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,使这些 API 应用的难度大大降低。
- 方便程序的测试
Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。
- AOP 编程的支持
Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。
- 声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无须手动编程。
2.Spring体系结构
- Data Access/Integration(数据访问/集成)
数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
- JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了在开发过程中对数据库操作的编码。
- ORM 模块:对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成层。
- OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。
- JMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息。
- Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的 POJO。
- Web 模块
Spring 的 Web 层包括 Web、Servlet、Struts 和 Portlet 组件,具体介绍如下。
- Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IoC 容器初始化以及 Web 应用上下文。
- Servlet模块:包括 Spring 模型—视图—控制器(MVC)实现 Web 应用程序。
- Struts 模块:包含支持类内的 Spring 应用程序,集成了经典的 Struts Web 层。
- Portlet 模块:提供了在 Portlet 环境中使用 MV C实现,类似 Web-Servlet 模块的功能。
- Core Container(核心容器)
Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 Expression Language 表达式语言模块组成,具体介绍如下。
- Beans 模块:提供了 BeanFactory,是工厂模式的经典实现,Spring 将管理对象称为 Bean。
- Core 核心模块:提供了 Spring 框架的基本组成部分,包括 IoC 和 DI 功能。
- Context 上下文模块:建立在核心和 Beans 模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。
- Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。
- 其他模块
Spring的其他模块还有 AOP、Aspects、Instrumentation 以及 Test 模块,具体介绍如下。
- AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
- Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
- Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
- Test 模块:支持 Spring 组件,使用 JUnit 或 TestNG 框架的测试。
3.依赖jar包(在maven的管理下会导入其他的依赖)
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.5</version></dependency>
4.IOC反转控制[重点]
首先,要明白什么是IOC,为什么要使用IOC?
例子:现在与有一个WebApp,发布给用户时,由于用户可能有多个,或是用户可能会变卦,会有用多种数据库连接方式:mysql,oracle,sqlserver等
模拟一个User实体类的操作(entity层,mapper层,service层)
结构:
user实体类:
package com.xiao.entity;public class User {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}}
mapper接口与三种实现:
package com.xiao.mapper;import com.xiao.entity.User;//模拟数据库的mapperpublic interface UserMapper {public void getUser();}
package com.xiao.mapper;//用mysql数据库获取Userpublic class UserMapperMysqlImpl implements UserMapper{public void getUser() {System.out.println("用mysql数据库获取User");}}
package com.xiao.mapper;//用Oracle数据库获取Userpublic class UserMapperOracleimpl implements UserMapper{public void getUser() {System.out.println("用Oracle数据库获取User");}}
package com.xiao.mapper;//用SqlServer数据库获取获取Userpublic class UserMapperSqlserverImpl implements UserMapper {public void getUser() {System.out.println("用SqlServer数据库获取获取User");}}
service(业务层)接口与实现
这里是mysql数据库连接的实现情况
package com.xiao.service;public interface UserService {public void getUser();}
package com.xiao.service;import com.xiao.mapper.UserMapper;import com.xiao.mapper.UserMapperMysqlImpl;public class UserServiceImpl implements UserService {UserMapper userMapper = new UserMapperMysqlImpl();public void getUser() {userMapper.getUser();}}
测试:
import com.xiao.service.UserServiceImpl;public class MyTest {public static void main(String[] args) {new UserServiceImpl().getUser();}}
结果:
但是,如果用户要求发送了改变呢?比如需要用sqlserver连接,我们就不得不修改源代码
也就是说,现在控制权在程序员手里,而且这样的情况下,如果代码量极大,就会出现牵动发而动全身的问题,而且用户的体验也不好。
所以,我们尝试吧控制权交给用户,解决方案是什么呢?
加入set!
package com.xiao.service;import com.xiao.mapper.UserMapper;public class UserServiceImpl implements UserService {UserMapper userMapper = null;public void setUserMapper(UserMapper userMapper) {this.userMapper = userMapper;}public void getUser() {userMapper.getUser();}}
这样,User的业务实现就可以直接通过set修改,如下:
import com.xiao.mapper.UserMapperMysqlImpl;import com.xiao.service.UserServiceImpl;public class MyTest {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();userService.setUserMapper(new UserMapperMysqlImpl());userService.getUser();}}
现在,如果客户想要修改实现方式,就只需要调用set方法就好了!
这就是ICO的本质
5.IOC容器
Configuration Metadata
三种配置方式:
- XML-based metadata
- Annotation-based configuration[Spring2.5以后支持]
- Java-based configuration[Spring30.以后支持]
6.XML-based metadata
the basic structure of XML-based configuration metadata::
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="..." class="..."><!-- collaborators and configuration for this bean go here --></bean><bean id="..." class="..."><!-- collaborators and configuration for this bean go here --></bean><!-- more bean definitions go here --></beans>
| 1 | The id attribute is a string that identifies the individual bean definition. |
|---|---|
| 2 | The class attribute defines the type of the bean and uses the fully qualified classname. |
6.1.Composing XML-based Configuration Metadata
<beans><import resource="services.xml"/><import resource="resources/messageSource.xml"/><import resource="/resources/themeSource.xml"/><bean id="bean1" class="..."/><bean id="bean2" class="..."/></beans>
在前面的示例中,外部bean定义从三个文件加载:services.xml、messageSource.xml和themeSource.xml。所有位置路径都是相对于执行导入操作的定义文件的,因此
services.xml必须与执行导入操作的文件位于相同的目录或类路径位置,
而messageSource.xml和themeSource.xml必须位于导入文件位置下方的资源位置。
当然,前导斜杠被忽略。但是,考虑到这些路径是相对的,最好不要使用斜杠。
6.2.Using the Container
方式1:
// create and configure beansApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");// retrieve configured instancePetStoreService service = context.getBean("petStore", PetStoreService.class);// use configured instanceList<String> userList = service.getUsernameList();
方式2:
//获取一个通用的IOC容器GenericApplicationContext context = new GenericApplicationContext();//创建XmlBeanDefinitionReader对象将指定的xml资源加载到IOC容器里面new XmlBeanDefinitionReader(context).loadBeanDefinitions("Beans.xml");//刷新容器[必须有]context.refresh();//取出BeanHello hello = context.getBean("hello", Hello.class);System.out.println(hello.toString());
7.Beans
Beans就是实体类,IOC容器里面存放的就是实体类的实例
7.1.Set注入
package com.xiao.entity;import java.util.*;public class Person {private String name;//引用类型private int age;//基本类型private Address address;//复杂对象private String[] books;//数组类型private List<String> friends;//列表private Set<String> families;//集合private Map<String,String> info;//mapprivate Properties properties;//配置属性private String wife = null;[getter and setter......]@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", address=" + address +", books=" + Arrays.toString(books) +", friends=" + friends +", families=" + families +", info=" + info +", properties=" + properties +", wife='" + wife + '\'' +'}';}}
Bean配置
<bean id="address" class="com.xiao.entity.Address"><property name="address" value="安徽"/></bean><bean id="person" class="com.xiao.entity.Person"><property name="name" value="肖云飞"/><property name="age" value="18"/><property name="address" ref="address"/><property name="books"><array><value>红楼梦</value><value>三国演义</value><value>西游记</value><value>水浒传</value></array></property><property name="friends"><list><value>berry</value><value>tom</value><value>lily</value></list></property><property name="families"><set><value>mom</value><value>dad</value><value>brother</value></set></property><property name="info"><map><entry key="身份证号" value="1222534961"></entry><entry key="学生证号" value="612349565861"></entry></map></property><property name="properties"><props><prop key="身高">175</prop><prop key="体重">70</prop></props></property><property name="wife"><null/></property></bean>
7.1.2Inner Bean
上面的复杂对象属性的注入可以用Inner Bean
<bean><property name="address"><bean class="com.xiao.entity.Address"><property name="address" value="Hello Spring"/></bean></property></bean>
7.2.构造器注入
package examples;public class ExampleBean {// Number of years to calculate the Ultimate Answerprivate int years;// The Answer to Life, the Universe, and Everythingprivate 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>
通过索引注入[The index is 0-based.]
<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg index="0" value="7500000"/><constructor-arg index="1" value="42"/></bean>
通过参数名注入[习惯]
<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg name="years" value="7500000"/><constructor-arg name="ultimateAnswer" value="42"/></bean>
7.3.其他注入方式
p-namespace[对标set注入]
使用前在xml头里面加上这一条
xmlns:p="http://www.springframework.org/schema/p"即
<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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd">......</beans>
对比:[使用p命名空间后可以不用properitiy标签]
<bean id="hello" class="com.xiao.entity.Hello"><property name="name" value="Hello Spring"/></bean><bean id="p_hello" class="com.xiao.entity.Hello" p:name="Hello Spring"></bean>
c-namespace[对标construct注入]
使用前在xml头里面加上这一句
xmlns:c="http://www.springframework.org/schema/c"即
```xml
对比:[使用p命名空间后可以不用constructor-arg标签]```xml<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="something@somewhere.com"/></bean><!-- c-namespace declaration with argument names --><bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
7.4. Lazy-initialized Beans
By default,
ApplicationContextimplementations eagerly create and configure all singleton beans as part of the initialization process.[默认情况下,配置时就会初始化bean]A lazy-initialized bean tells the IoC container to create a bean instance when it is first requested, rather than at startup.[azy-initialized 可以让bean在第一次被调用时才创建]
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/><bean name="not.lazy" class="com.something.AnotherBean"/>
can also control lazy-initialization at the container level by using the
default-lazy-initattribute on the<beans/>element, as the following example shows:[可以在beans标签里面控制]
<beans default-lazy-init="true"><!-- no beans will be pre-instantiated... --></beans>
8.Bean自动装配
Autowiring can significantly reduce the need to specify properties or constructor arguments.
Autowiring can update a configuration as your objects evolve.
使用时只需要在bean标签里面添加autowire属性,autowire有四种方式
| Mode | Explanation |
|---|---|
no |
(Default) No autowiring. Bean references must be defined by ref elements. Changing the default setting is not recommended for larger deployments, because specifying collaborators explicitly gives greater control and clarity. To some extent, it documents the structure of a system. |
byName |
Autowiring by property name. Spring looks for a bean with the same name as the property that needs to be autowired. For example, if a bean definition is set to autowire by name and it contains a master property (that is, it has a setMaster(..) method), Spring looks for a bean definition named master and uses it to set the property. |
byType |
Lets a property be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring for that bean. If there are no matching beans, nothing happens (the property is not set). |
constructor |
Analogous to byType but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised. |
注意:
- 使用byName时,名字必须全局唯一
- 使用byType时,同一种类型只能有一个Bean
9.BeanScope
9.1六种Scope
| Scope | Description |
|---|---|
| singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
| prototype | Scopes a single bean definition to any number of object instances. |
| request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext. |
| session | Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext. |
| application | Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext. |
| websocket | Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext. |
9.1.1The Singleton Scope
9.1.2The Prototype Scope
10.Annotation-based Container Configuration
Annotation injection is performed before XML injection
需要的配置
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/></beans>
11.@Autowired[自动装配]
作用:自动装配[注意:这个注解同时包含了byName与byType两种自动装配能力]
范围:属性,构造方法,任意方法,方法返回值类型,方法参数值类型,也可以在不同地方同时使用
注意:
默认情况下,同上面的XML中注入一样,必须在xml里面存在对应的Bean存在才能自动装配
但是,有一种特殊情况:即使在属性上使用了@Autowired但没有对应的Bean可以装配,说明这个属性不是十分必要的(要是必要就一定会有了),这是就可以使用@Autowired(required = “false”) 声明,就可以在没有Bean的情况下装配一个null,而不会报错。【效果等价于配置了一个包含< null/ >标签的Bean】
上面是没有Bean可以使用的情况,更常见的是有多于一个的同类型Bean可以被装配[id相同,而且类型相同的Bean存在直接报错],有两种方案
使用@Primary 【作用范围很小,只能使用在方法与返回值类型上面】,需要同时在Bean里面申明
<bean class="com.xiao.entity.Book" p:name="《哈哈哈》"></bean><bean class="com.xiao.entity.Book" p:name="《嘿嘿嘿》" primary="true"></bean>
/*这样就会找到《嘿嘿嘿》这本书了*/public @Primary Book getBook() {return book;}
/*当然,也可以在方法上面申明,效果一样*/@Primarypublic Book getBook() {return book;}
使用@Qualifier(“……”),可以发现:上面的方法有两个局限性
- 使用范围小【很重要的属性不能使用】。
- 只能指定一个,而大部分时候,有优先选择权的Bean并不是一个或直接没有。
- 为了更加灵活的调用多个同类型的Bean,@Qualifier(“……”)诞生了,实现方式类似于给会受到选择的Bean加一个修饰符【不同的Bean使用不一样的修饰符】,这样就可以通过修饰符来指定装配了
- 同时@Qualifier(“……”)的范围与@Autowired一样,比@Primary大得多
<bean class="com.xiao.entity.Dog" p:age="5" primary="true"><qualifier value="main"/></bean><bean class="com.xiao.entity.Dog" p:age="6" ><qualifier value="second"/></bean>
/*方法上使用*/@Autowired@Qualifier("second")private Dog dog;
/*返回值类型使用*/@Beanpublic @Qualifier("main") Dog getDog() {return dog;}
/*方法上使用*/@Qualifier("main")public void setDog(Dog dog) {this.dog = dog;}
注意,@Qualifier与@Primary是可以同时使用的
既然@Qualifier与@Primary可以同时使用,那就会有一个优先级,
- 首先@Primary只能在方法或方法的返回类型,元素类型上使用
- @Qualifier可以直接在属性上面约束,
- 并且,当出现相同类型的Bean等待被装配(没有id属性的Bean)时,如果不对有这种Bean依赖的属性显示申明【就是用@Qualifier】,就会直接报错
- 由上面的结论,可以想到只能是==@Qualifier的优先级更高==,经过测试也的确如此。
- 【当然,这是在方法与属性指向同一个Bean的情况下,如果某一个方法使用了一个与所属类的属性不同的Bean时就可以装配另一个】
实际上,@Qualifier与@Primary的应用场合也比较显然,@Primary只是对某一个方法或方法的返回值,参数值的一个强调。
@Qualifier特殊用法[直接把@Qualifier的Bean类型配置一个别名注解]
1.没有属性的别名注解
这是被依赖的即将要被配置的Bean实体类
package com.xiao.entity;public class Dog {private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Dog{" +"age=" + age +'}';}}
下面这两个完成了将@Qualifier(value = “main”)向@Main的简化
package com.xiao.qualifier;import org.springframework.beans.factory.annotation.Qualifier;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Qualifierpublic @interface Main {}
<bean class="com.xiao.entity.Dog" p:age="5" primary="true"><qualifier type="com.xiao.qualifier.Main"/></bean>
这种别名注解显然一个问题:
- 这样的别名只能配置到一个Bean实例上面,如果出现了两个完全一样的Bean就出错了(当然找不到)
向这样的配置就直接报错了
<bean class="com.xiao.entity.Dog" p:age="5" ><qualifier type="com.xiao.qualifier.Main"/></bean><bean class="com.xiao.entity.Dog" p:age="6" ><qualifier type="com.xiao.qualifier.Main"/></bean>
当然,再加上其他的约束也可以,如下(这里只是在这里加上了primary=”true”就可以装配到这个,说明primary属性可以在有多个同类型Bean共存时优先级更高)
<bean class="com.xiao.entity.Dog" p:age="5" ><qualifier type="com.xiao.qualifier.Main"/></bean><bean class="com.xiao.entity.Dog" p:age="6" primary="true"><qualifier type="com.xiao.qualifier.Main"/></bean>
或是这样(不同于上面的primary,这里需要在依赖的Bean里面加上@Qualifier(value = “main2”)才可以)
<bean class="com.xiao.entity.Dog" p:age="5" ><qualifier type="com.xiao.qualifier.Main"/></bean><bean class="com.xiao.entity.Dog" p:age="6" ><qualifier type="com.xiao.qualifier.Main"/><qualifier value="main2"/></bean>
2.有一个属性的别名注解
上面鹅问题里面,正是因为这个注解别名只有一个名字作为辨别,所以限制较大,那么,在这个别名里面再加上一个属性不就可以放宽限制了吗?
当然:只有一个属性时,属性名必须是value[这是约定,当然也可以不是]
package com.xiao.qualifier;import org.springframework.beans.factory.annotation.Qualifier;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Qualifierpublic @interface Main {String value();}
<bean class="com.xiao.entity.Dog" p:age="5" ><qualifier type="com.xiao.qualifier.Main" value="first"></qualifier></bean><bean class="com.xiao.entity.Dog" p:age="6" ><qualifier type="com.xiao.qualifier.Main" value="second"></qualifier></bean>
这样就可以找到特定的Bean实例了
@Main("first")private Dog dog;
有上面可以看出:这种注解别名实际上就是给@Qualifier的Qualifier换了个名字
3.有多个属性的别名直接
上面的配置里面只有一个属性value,现在就是在这个的基础上多加几个
package com.xiao.qualifier;import org.springframework.beans.factory.annotation.Qualifier;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Qualifierpublic @interface Main {String value();int age();}
<bean class="com.xiao.entity.Dog" p:age="5" ><qualifier type="com.xiao.qualifier.Main"><attribute key="value" value="first"/><attribute key="age" value="5"/></qualifier></bean><bean class="com.xiao.entity.Dog" p:age="6" ><qualifier type="com.xiao.qualifier.Main"><attribute key="value" value="second"/><attribute key="age" value="6"/></qualifier></bean><bean class="com.xiao.entity.Dog" p:age="7" ><qualifier type="com.xiao.qualifier.Main"><attribute key="value" value="second"/><attribute key="age" value="7"/></qualifier></bean>
当然value属性可以直接作为qualifier标签的属性指明,如下
<bean class="com.xiao.entity.Dog" p:age="5" ><qualifier type="com.xiao.qualifier.Main" value="first"><attribute key="age" value="5"/></qualifier></bean><bean class="com.xiao.entity.Dog" p:age="6" ><qualifier type="com.xiao.qualifier.Main" value="second"><attribute key="age" value="6"/></qualifier></bean><bean class="com.xiao.entity.Dog" p:age="7" ><qualifier type="com.xiao.qualifier.Main" value="second"><attribute key="age" value="7"/></qualifier></bean>
不论是一个属性还是多个属性,只有为value的属性名可以直接配置在qualifier标签里面,而不是子标签。
12.@Resources[通过id或primary属性装配]
上面的@Autowired实现了自动装配,但是最初的在XML里面依赖注入是使用ref来注入另一个Bean(当然也可以是Inner Bean)的,注解的方式当然也可以,那就是@Resources
作用范围:Type,filed,method
@Resources寻找Bean的方式
- 是先通过@Resources(name = “……”)里面给出的名字与Ioc容器里面的Bean的id匹配,【 一旦制定了name,Ioc容器里面就必须有指定的id与之匹配,否则直接报错】
- 如果没有显示指定name属性,Spring也可以隐式地寻找———
- 如果使用在filed上就把filed的名字作为隐式的name去寻找,
- 如果在method上使用就把set的名字作为隐式的name去寻找【这一点与myBatis有些类似】
- 当然第二步即便没有通过隐式的name匹配到Bean,也不会直接报错,会再一次通过Primary属性来判断
- 如果第三步都没有匹配到,就报错了。
第一步找到情况:
@Resource(name = "book1")private Book book;
<bean id = "book1" class="com.xiao.entity.Book" p:name="哈哈哈"></bean><bean id = "book2" class="com.xiao.entity.Book" p:name="嘿嘿嘿"></bean>
第二步找到情况:
@Resourcepublic void setBook(Book book1) {this.book = book1;}//filed与method的情况类似,会把book作为隐式name@Resourceprivate Book book;
<bean id = "book" class="com.xiao.entity.Book" p:name="哈哈哈"></bean><bean id = "book2" class="com.xiao.entity.Book" p:name="嘿嘿嘿"></bean>
第三步找到的情况:
@Resourceprivate Book book;@Resourcepublic void setBook(Book book) {this.book = book;}//会把book作为隐式name,但是匹配不到Bean,于是寻找有primary="true"的Bean
<bean id = "book1" class="com.xiao.entity.Book" p:name="哈哈哈"></bean><bean id = "book2" class="com.xiao.entity.Book" p:name="嘿嘿嘿" primary="true"></bean>
13.@Value[对基本类型与String引用类型的装配]
已经研究过了自动转配与装配Bean的情况,还有更常见的情况:对基本类型与String引用类型的装配
@Value范围:filed,method,parameter
直接用法:
@Value("肖云飞")private String name;
但是有一个问题{这是@Value的源码}:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Value {/*** The actual value expression such as <code>#{systemProperties.myProp}</code>* or property placeholder such as <code>${my.app.myProp}</code>.*/String value();}
显而易见:value的类型只能是String,那int,float,Boolean等类型怎么办?
有两种办法;
- 将要配置的值放在配置文件里面再读取进来
- 使用SPEL表达式
13.1.1第一种的直接实现:
@PropertySource("info.properties")public class Student {@Value("${name}")private String name;}
name=XiaoyunFeiage=20
13.1.2第一种的间接实现
1.将配置放在一个单独的类里面
@Configuration@PropertySource("info.properties")public class AppConfig {}
2.==重要!!!==将这个类交给Spring管理
<bean id = "appConfig" class="com.xiao.config.AppConfig"></bean>
3.现在就可以直接注入了
public class Student {@Value("${name}")private String name;@Value("${age}")private int age;
13.1.3第一种使用Environment实现
只要是如下格式的类并且交给Spring管理,值得的配置文件里面的配置都会被封装在Environment里面,可以通过Environment获得【这是PropertySource源码中的实例】
@Configuration@PropertySource("info.properties")public class AppConfig {@Autowiredprivate Environment env;public Student setName(Student student){student.setName(env.getProperty("name"));return student;}}
必须注意,由于只有被Spring管理的Bean的如@Configuration等注解才会起作用,所以要是用前
- 确保这个Bean是Spring管理的
- 确保是从Ioc容器里面取出的Bean
public class Student {//先注入一个123@Value("123")private String name;@Value("${age}")
public class MyTest {public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();new XmlBeanDefinitionReader(context).loadBeanDefinitions("Beans.xml");context.refresh();//获取appConfigAppConfig appConfig = context.getBean("appConfig", AppConfig.class);//获取studentStudent student = context.getBean("student", Student.class);System.out.println(student.getName());//修改Student student1 = appConfig.setName(student);System.out.println(student1.getName());System.out.println(student);}}
结果:
13.2第二种:
@Value("#{18}")private int age;
{}里面可以是’字符串’,18(数字),5+2(表达式),false(Boolean),T(com.xiao.config.AppConfig).get()[调用静态方法]等……
尝试用SPEL获取properties里面的属性并注入
(我的尝试,估计一般不用)
工具类:
package com.xiao.config;import org.springframework.context.annotation.Configuration;import java.io.IOException;import java.io.InputStream;import java.util.Properties;@Configurationpublic class AppConfig {public static int getIntProp(String fileLocation,String propName) {InputStream inputStream = AppConfig.class.getClassLoader().getResourceAsStream(fileLocation);Properties properties = new Properties();try {properties.load(inputStream);} catch (IOException e) {e.printStackTrace();}String age = properties.getProperty(propName);return new Integer(age);}}
public class Student {@Value("#{T(com.xiao.config.AppConfig).getIntProp('info.properties','age')}")private int age;}
{T(com.xiao.config.AppConfig).getIntProp(‘info.properties’,’age’)}实现了取出配置值
14.Spel表达式
1.简介
SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。它可以在运行时查询和操作数据,尤其是数组列表型数据,因此可以缩减代码量,优化代码结构。
2.用法
SpEL有三种用法,一种是在注解==@Value 中;一种是XML配置==;最后一种是在代码块中使用Expression。
其中@Value的用法在上面用过了,在XML里面实际上与@Value差不多
@Value("#{18}")private int age;
<bean id="person" class="com.xiao.entity.Person"><!-- 同@Value,#{}内是表达式的值,可放在property或constructor-arg内 --><property name="age" value="#{18}"></bean>
代码中使用:
public class MyTest {public static void main(String[] args) {//创建一个用于解析表达式的对象paserExpressionParser paser= new SpelExpressionParser();//解析后产生一个Expression对象(即表达式放置),默认容器是spring本身的容器:ApplicationContextExpression expression = paser.parseExpression("Hello World");//获取解析结果System.out.println(expression.getValue());}}
3.表达式语法
3.1直接赋值
<!-- 整数 --><property name="count" value="#{5}" /><!-- 小数 --><property name="frequency" value="#{13.2}" /><!-- 科学计数法 --><property name="capacity" value="#{1e4}" /><!-- 字符串 #{"字符串"} 或 #{'字符串'} --><property name="name" value="#{'我是字符串'}" /><!-- Boolean --><property name="enabled" value="#{false}" />
注: 1)字面量赋值必须要和对应的属性类型兼容,否则会报异常。
2)一般情况下我们不会使用 SpEL字面量赋值,因为我们可以直接赋值。(当然@Value时可以用)
3.2.引用Bean、属性和方法(必须是public修饰的)
<property name="car" value="#{car}" /><!-- 引用其他对象的属性 --><property name="carName" value="#{car.name}" /><!-- 引用其他对象的方法 --><property name="carPrint" value="#{car.print()}" />
4.运算符
4.1算术运算符
+,-,*,/,%,^
<!-- 3 --><property name="num" value="#{2+1}" /><!-- 1 --><property name="num" value="#{2-1}" /><!-- 4 --><property name="num" value="#{2*2}" />
4.2逻辑运算符
and,or,not,!
<!-- false --><property name="numBool" value="#{true and false}" /><!-- false --><property name="numBool" value="#{true&&false}" /><!-- true --><property name="numBool" value="#{true or false}" />
4.3比较运算符
<(<),>(>),==,<=,>=,lt,gt,eq,le,ge
<!-- false --><property name="numBool" value="#{10<0}" /><!-- false --><property name="numBool" value="#{10 lt 0}" /><!-- true --><property name="numBool" value="#{10>0}" /><!-- true --><property name="numBool" value="#{10 gt 0}" />
4.4字符串连接符
<!-- 10年3个月 --><property name="numStr" value="#{10+'年'+3+'个月'}" />
4.5条件运算符(三元运算符)
?true:false
<!-- 真 --><property name="numStr" value="#{(10>3)?'真':'假'}" />
5调用静态方法或静态属性
通过 T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性[即反射]
<!-- 3.141592653589793 --><property name="PI" value="#{T(java.lang.Math).PI}" />
6.集合相关表达式
List
@Value("#{{'小红','小明'}}")private List<String> friends;//列表
Set
@Value("#{{'mom','mom','dad'}}")private Set<String> families;//集合
Map
private Set<String> families;//集合@Value("#{{'身份证号':123456789,'学生证':987654321}}")
Array
@Value("#{{'三国演义','西游记','红楼梦'}}")private String[] books;//数组类型
或者:
@Value("#{new String[3]{'三国演义','西游记','红楼梦'}}")private String[] books;//数组类型
测试:
public class MyTest {public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();new XmlBeanDefinitionReader(context).loadBeanDefinitions("Beans.xml");context.refresh();Person person = context.getBean("person", Person.class);System.out.println(person.getFriends());System.out.println(Arrays.toString(person.getBooks()));System.out.println(person.getFamilies());System.out.println(person.getInfo());}}//结果:/**[小红, 小明][三国演义, 西游记, 红楼梦][mom, dad]{身份证号=123456789, 学生证=987654321}*/
但是,有一个重要的点:
上面的列表是不能修改的,其他的没有问题。
原因:List里面注入的是表面量。
int a = 10;final b = "123";//a是变量,b是常量,10和"123"都是表面量【就是数字,字符等直接显示的量】
15.Java-Based-configration
1.@Component
@Compoment的作用实际上就是在IOC容器里面托管了一个Bean
@Compoment相当于< Bean class=”” > < /Bean >
@Compoment(“name”)相当于< Bean id=”” class=”” > < /Bean >
Spring提供了进一步实现@Compoment的三个注解:
- @Repository:表示为持久层的Bean
- @Service:表示为业务层的Bean
- @Controller:表示为控制层的Bean
注意,使用@Compoment并且使用xml配置里面就必须有下面的配置:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!-- 由 Spring容器创建该类的实例对象 --><!-- 申明使用注解 --><context:annotation-config/><!-- 定位使用@Compoment的包--><context:component-scan base-package="com.xiao"></context:component-scan>
如果使用java类作为IOC容器的配置的话就不用了上面的了
2.@Bean
@Bean的作用与@Componnent的实际作用是相同的,都是将指定的类交给IOC容器管理,装配
@Bean就相当于< Bean class=”” > < /Bean >
@Bean(“name”)就相当于< Bean id=”” class=”” > < /Bean >
不同点:
- 作用范围不同:@Bean用在方法上,@Component用在类上
- 作用位置不同:@Bean一般与@Configration配套使用,@Component一般可以用在除配置类外的所有实体类上
- @Bean是隐式申明:在配置类的获取Bean的方法上使用,表示这个方法的实现需要Spring去装配指定的Bean的逻辑,由此将Bean的定义与声明分开,@Component是显示申明,用在Bean对应的类上。
重要的一个相同点:
如果只使用注解与java配置,那么Bean的初始化装配就只能通过@Autowired或@Resources或@Value,
而由于这个类使用@Autowired或@Resources或@Value装配时只能装配特定的值,所以存在一对一的映射,
也就是说,这种情况从IOC容器里面取出的Bean只会是相同的值,
如果想要取出不同值的同种Bean(不考虑去除之后修改),那就在装配时不要使用注解显示申明,在配置类获取Bean的方法里面设置。
虽然有这些不同,但@Bean与@Component的目的是相同的,并且对于同一个Bean来说,只要有了@Bean与@Component两者的任意一个就不再需要另一个了,那为什么还要有两个呢?
@Component是Spring2.5提出的,@Bean是Spring3.0提出的,显然@Bean是对@Component的某种补充,
上面提到了:@Component只能用在Bean对应的实体类上,那如果我们要用第三方库的类作为注入对象呢?
就比如下面的Person实体类有一个属性是java.util.Date显然@Component是不可能加在Date源码上面的,想要实现注入,就可以使用@Bean实现(当然可以使用SPEL表达式)
java配置实例:
package com.xiao.entity;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Repository;import java.util.Date;@Repository("person")public class Person {@Value("XiaoYunfei")private String name;@Value("#{19}")private int age;@Value("#{new java.util.Date()}")@Autowired //这种必须是在XML或java配置类(使用@Bean)里面装配过才可以使用private Date timeNow;@Autowiredprivate Dog dog;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Date getTimeNow() {return timeNow;}public void setTimeNow(Date timeNow) {this.timeNow = timeNow;}public Dog getDog() {return dog;}public void setDog(Dog dog) {this.dog = dog;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", timeNow=" + timeNow +", dog=" + dog +'}';}}
package com.xiao.entity;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Component("dog")public class Dog {@Value("pot")private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Dog{" +"name='" + name + '\'' +'}';}//重写equals方法,为下面测试做准备@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Dog dog = (Dog) o;return Objects.equals(name, dog.name);}}
配置类:
package com.xiao.config;import com.xiao.entity.Dog;import com.xiao.entity.Person;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import java.util.Date;@Configuration@ComponentScan(basePackages = "com.xiao")public class AppConfig {@Bean("person")public Person getPerson(){return new Person();}@Bean("dog")public Dog getDog(){Dog dog = new Dog();dog.setName("pot");return dog;}/*下面这个Bean是用来测试的*/@Bean("dog2")@Primarypublic Dog getDog2(){Dog dog2 = new Dog();dog2.setName("berry");return dog2;}/*通过下面的配置,实现了在Spring容器里面注册了一个Date的Bean*/@Beanpublic Date getTimeNow(){return new Date();}}
3.@Configration
@Configration的作用就是申明这个类作为一个Spring容器存在,在里面定义一些Bean(在对应Bean没有使用@Component时要使用@Bean),而且获取Bean的方法必须是public的
3.1@ComponentScan(basePackages = “com.xiao”)
用在@Configration指定的配置类上,效果相当于XML配置里面的:
<!-- 定位使用@Compoment的包--><context:component-scan base-package="com.xiao"></context:component-scan>
3.2测试一对一映射
3.2.1简单属性的测试
对于上面的dog2的测试,是对@Bean与@Component是一对一映射的验证:
我们在Dog类里面用@Value装配了name的值,但在配置类里面我们尝试获取Spring装配好的通过dog的setName方法修改dog的name之后获取一个新的dog
@Bean("dog2")@Primarypublic Dog getDog2(){Dog dog2 = new Dog();dog2.setName("berry");return dog2;}
测试发现:通过dog2获取的Dog实例的name还是pot,说明 dog2.setName(“berry”);这句话没有起作用,
进一步测试:(默认是单例模式,验证获取到的Dog是不是同一个)
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Dog dog = context.getBean("dog",Dog.class);Dog dog2 = context.getBean("dog2",Dog.class);Dog dog3 = context.getBean("dog2",Dog.class);System.out.println(dog == dog2);System.out.println(dog.equals(dog2));System.out.println(dog2 == dog3);System.out.println(dog);System.out.println(dog2);//结果/*false 说明:不同的获取方法,即便实现是一样的,获取到的引用也不一样true 说明:set方法的确没有起作用true 说明:的确是单例模式,这也说明不是单例模式导致的set没有作用Dog{name='pot'}Dog{name='pot'}*/
如果把@Value删掉呢?
@Component("dog")public class Dog {private String name;}
@Bean("dog")public Dog getDog(){Dog dog = new Dog();dog.setName("pot");return dog;}@Bean("dog2")public Dog getDog2(){Dog dog2 = new Dog();dog2.setName("berry");return dog2;}
测试程序不变:
Dog dog = context.getBean("dog",Dog.class);Dog dog2 = context.getBean("dog2",Dog.class);Dog dog3 = context.getBean("dog2",Dog.class);System.out.println(dog == dog2);System.out.println(dog.equals(dog2));System.out.println(dog2 == dog3);System.out.println(dog);System.out.println(dog2);//结果:/*falsefalsetrueDog{name='pot'}Dog{name='berry'} 最后的输出说明set方法起作用了*/
3.2.2复杂对象属性的测试
再进一步测试,Person这个Bean有一个Dog属性,对这个属性进行相同的操作
public class Person {......@Resource//会自动装配id为dog的Beanprivate Dog dog;......}
@Bean("person")public Person getPerson(){return new Person();}@Bean("person2")public Person getPerson2(){Person person2 = new Person();person2.setDog(getDog2());//尝试改变默认的装配dog为新的dog,此时Dog类没有@Value,也就是说set方法是可以起作用的return person2;}@Bean("dog2")public Dog getDog2(){Dog dog2 = new Dog();dog2.setName("berry");return dog2;}
测试:
Person person = context.getBean("person",Person.class);Person person2 = context.getBean("person2",Person.class);System.out.println(person.getDog());System.out.println(person2.getDog());//结果:/*Dog{name='pot'}Dog{name='pot'} 同上面一样set方法没有起作用*/
同样的尝试把@Resources删掉
public class Person {......//不会自动装配,默认为nullprivate Dog dog;......}
测试代码相同,结果:
/*nullDog{name='berry'} 符合预期*/
不仅如此,不论是修改作用域为prototype(非单例模式)还是注解为懒加载,结果都不会变。
有上面的测试,完成了对一对一映射的猜想,但原因是什么呢?
经过下面的更近一步测试,我认为
- 不是是方法中的set没起作用,而是return没起作用【这也证明了为什么使用同一个方法获取Bean时可以获取到同一个,即单例】,虽然这并不会因为修改为非单例模式而改变.
- 配置类中的一切操作优先级低于直接在实体类中的注解
- 由2可以推出:不论是使用Set方法还是使用构造器,如果已经在实体类中有装配,直接无视;如果没有,会使用set或构造器产生的值,如果都没有,就使用默认值【当然,如果使用了@Autowired(required = true)就必须配置了。】
3.2.3最终验证
下面四种情况分别是[name属性使用@Value,age属性不使用@Value]:
- 在配置类里面提前new一个对象,在方法中返回
- 在方法中无参构造,再使用使用set方法
- 构造器创造新对象返回
- 使用clone产生一个新对象返回
public class Dog implements Cloneable{@Value("pot")private String name;private int age;......}
public class AppConfig {private Dog dog0 = new Dog("lily",9);@Bean("dog0")public Dog getDog0(){return dog0;}@Bean("dog")public Dog getDog(){Dog dog = new Dog();dog.setName("hanny");dog.setAge(7);return dog;}@Bean("dog2")public Dog getDog2(){Dog dog2 = new Dog("berry",6);return dog2;}@Bean("dog3")public Dog getDog3(){Dog dog3 = null;try {dog3 = new Dog().clone();System.out.println("克隆完成");} catch (CloneNotSupportedException e) {e.printStackTrace();}//下面两行验证set方法起作用了dog3.setName("tex");System.out.println(dog3.getName().equals("tex"));return dog3;}}
测试:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Dog dog0 = context.getBean("dog0", Dog.class);Dog dog = context.getBean("dog", Dog.class);Dog dog2 = context.getBean("dog2", Dog.class);Dog dog3 = context.getBean("dog3", Dog.class);System.out.println(dog0);System.out.println(dog);System.out.println(dog2);System.out.println(dog3);//结果:/*克隆完成 由于不是懒加载,所以会在程序启动时就加载true 说明set方法的确起作用了Dog{name='pot', age=9}Dog{name='pot', age=7}Dog{name='pot', age=6}Dog{name='pot', age=0} name全部都是pot,age随意发生改变(未设置就是默认的0)*/
但是,不同于java实现装配的这种问题,xml配置时,就可与实现覆盖
<bean id="dog5" class="com.xiao.entity.Dog"><property name="name" value="berry"></property><property name="age" value="5"></property></bean>
ApplicationContext context2 = new ClassPathXmlApplicationContext("beans.xml");Dog dog1 = context2.getBean("dog5", Dog.class);System.out.println(dog1);//结果/*Dog{name='berry', age=5}*/
多个容器转装配(额外发现)
在测试这个时还发现了一个现象:
上面的完整的测试程序中实际上转配了两个容器
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);ApplicationContext context2 = new ClassPathXmlApplicationContext("beans.xml");输出时出现了这样的结果:
/**克隆完成true克隆完成true*/说明java配置中的Bean被同时读取了两遍
继续测试(用读取xml的容器获取java配置类里面的Bean):
Dog dog21 = context2.getBean("dog2", Dog.class);System.out.println(dog21);//结果: Dog{name='pot', age=6}(用读取java配置类的容器获取xml中的Bean)
Dog dog21 = context2.getBean("dog5", Dog.class);System.out.println(dog21);//报错,没有找到dog5这个Bean结论:
- ClassPathXmlApplicationContext可以同时读取xml文件与java配置类中的Bean,而且不用担心重名问题,重名时,xml文件中的Bean优先级高[查看源码发现ClassPathXmlApplicationContext的构造器有一个就可以传入class对象]
//可以这么写ApplicationContext context2 = new ClassPathXmlApplicationContext("beans.xml",AppConfig.class);//这也说明了上面的问题
- AnnotationConfigApplicationContext就只能读取到java配置类中的Bean.[可以想到,AnnotationConfigApplicationContext就没有上面的玩法,查看源码的确没有]
16.代理模式
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
目的:
- 实现对业务类(委托类)功能的拓展
下面是代理模式的学习model结构
模拟数据库增删改查代理
mapper:模拟model层(被代理的接口)
service:模拟业务层(真实对象)
static_proxy:静态代理类位置
dynamic_proxy:动态代理位置
- UserServiceProxyHandler:针对具体的代理对象实现
- ProxyHandler:拓展至可以动态生成不同的代理
161静态代理模式
package com.xiao.mapper;//模拟model层public interface UserMapper {public void add();public void delete();public void update();public void select();}
package com.xiao.mapper;public class UserMapperImpl implements UserMapper{public void add() {System.out.println("add了一个用户");}public void delete() {System.out.println("delete了一个用户");}public void update() {System.out.println("update了一个用户");}public void select() {System.out.println("select了一个用户");}}
package com.xiao.service;import com.xiao.mapper.UserMapper;import com.xiao.mapper.UserMapperImpl;//模拟service层public class UserService implements UserMapper {UserMapper userMapper = new UserMapperImpl();public void add() {userMapper.add();}public void delete() {userMapper.delete();}public void update() {userMapper.update();}public void select() { userMapper.select();}}
package com.xiao.static_proxy;import com.xiao.mapper.UserMapper;import com.xiao.service.UserService;//静态代理的是实现public class UserServiceProxy implements UserMapper {UserMapper userMapper = new UserService();public void add() {start("add");userMapper.add();end("add");}public void delete() {start("delete");userMapper.delete();end("delete");}public void update() {start("update");userMapper.update();end("update");}public void select() {start("select");userMapper.select();end("select");}public void start(String msg){System.out.println("开始执行"+msg+"方法......");}public void end(String msg){System.out.println(msg+"方法执行完成");}}
测试:
new UserServiceProxy().add();new UserServiceProxy().delete();/*开始执行add方法......add了一个用户add方法执行完成开始执行delete方法......delete了一个用户delete方法执行完成*/
优点:
- 业务层可以专注于业务的实现
- 对业务的拓展功能交给代理类来做【如日志功能等】
- 不需要修改业务的代码,提高安全性
缺点:
- 如果有很多业务类需要代理,代码量就会接近翻倍
16.2动态代理模式
动态代理就可以较好的解决静态代理的代码量太大的问题
package com.xiao.dynamic_proxy;import com.xiao.mapper.UserMapper;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;//这是一个针对具体的被代理接口的动态代理实现public class UserServiceProxyHandler implements InvocationHandler {//被代理的接口private UserMapper userMapper;//实际传入真实对象public void setUserMapper(UserMapper userMapper) {this.userMapper = userMapper;}//获取代理类实例(类加载器,被代理接口,this)public Object getProxy(){return Proxy.newProxyInstance(this.getClass().getClassLoader(), userMapper.getClass().getInterfaces(),this);}//代理类执行的程序(上一步获取到的代理类实例类型转换为被代理接口后直接调用自己的方法就会执行这个方法)public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开始执行"+method.getName()+"方法......");Object result = method.invoke(userMapper,args);System.out.println(method.getName()+"方法执行完毕");return result;}}
测试:
//虽然是代理接口,但传入的必须是真实对象UserMapper userService = new UserService();UserServiceProxyHandler userServiceProxyHandler = new UserServiceProxyHandler();userServiceProxyHandler.setUserMapper(userService);UserMapper proxy = (UserMapper) userServiceProxyHandler.getProxy();proxy.delete();/*开始执行delete方法......delete了一个用户delete方法执行完毕*/
显而易见,这样的话代码量比静态代理还大,需要改进。
首先,静态代理为什么代码量会大?可以想到因为一个静态的代理类只能代理一个业务类,导致代码量接近翻倍,
而动态代理的原理实际上就是通过反射,动态地获取接口对象的一切,然后生成代理对象,然后由代理对象执行接口对象的方法(实际上由于传入的是真实对象,执行的就是真实的业务与附加属性)
在上面的动态代理中,我们固定了传入的被代理接口只能是UserService,导致生成的代理对象只能是UserService的代理,这就容易想到,不去固定被代理的接口不就可以了吗?
那就把UserService全部改为Object类型不就实现了通配?
改进如下:
package com.xiao.dynamic_proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;//通过设置被代理接口为Object实现把这个类包装为一个工具类,可以用它来获得多种不同的代理public class ProxyHandler implements InvocationHandler {//这是要被代理的接口private Object target;//通过set最终实现不同target的设置public void setTarget(Object target) {this.target = target;}public Object getProxy(){return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开始执行"+method.getName()+"方法......");Object result = method.invoke(target,args);System.out.println(method.getName()+"方法执行完毕");return result;}}
测试:
UserMapper userService = new UserService();ProxyHandler proxyHandler = new ProxyHandler();proxyHandler.setTarget(userService);UserMapper proxy1 = (UserMapper) proxyHandler.getProxy();proxy1.add();/*开始执行add方法......add了一个用户add方法执行完毕*/
至此,动态代理就兼具了静态代理的优点,同时解决了静态代理的缺点。
17.AOP(面向切面编程)[重点]
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方<br />式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。
17.1几个重要的概念
- Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- Target(目标对象):织入 Advice 的目标对象.。
- Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
17.2对上面几个概念的理解(例子):
这是我对上面几个概念的理解:
现在有两个个业务类UserService与AddressService,这两个都是可能会被拓展功能的类,所以这两个类就成为了Joint point,
我们需要对UserService的selectAll()方法进行一些功能的拓展【如日志功能】,这时UserService就成为了 Target,
selectAll()方法就成为了point cut,
【可以这么说:joint point是构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化等程序中一切可能被横向拓展的东西,我们要冲从中选出真正需要拓展的点,就需要我们给出描述信息,符合描述信息的joint point就成为了point cut】,
具体要拓展的功能【这里是日志功能】就是Advice,
Advice与所有符合描述信息的joint point即pointcut就构成了一个Aspect.
一旦确定了切面,切面里面的point cut就会被包装为被增强的point cut(也可以说是被代理),接着把包装后的point cut代替原本的位置与其他的程序连接起来的过程就是weaving
这是静态代理的模型
这是动态代理的模型

把中间的红框内容提取出来就是一个Aspect
这就是Aop的模型
17.3 Advice的类型
(1)Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可。
(2)AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值。
(3)AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象。
(4)After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式。
(5)Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint。
17.4三种基于Spring的aop实现方式
首先需要一个jar包
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version></dependency>
xml配置头信息:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"></beans>
17.4.1基于java类的实现
基于java类的实现中:
- 需要的advice需要实现(或继承)接口(或类)
- 在xml里面配置pointcut与advice
前置advice实现
package com.xiao.class_based_aop.log.logBefore;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;public class LogBefore implements MethodBeforeAdvice {public void before(Method method, Object[] args, Object target) throws Throwable {System.out.println(target.getClass().getName()+"的"+method.getName()+"方法开始执行......");}}
后置advice实现
package com.xiao.class_based_aop.log.logAfter;import org.springframework.aop.AfterReturningAdvice;import java.lang.reflect.Method;public class LogAfter implements AfterReturningAdvice {public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {System.out.println(target.getClass().getName()+"的"+method.getName()+"方法执行完成");}}
接口
package com.xiao.class_based_aop.mapper;//模拟model层public interface UserMapper {public void add();public void delete();public void update();public void select();}
真实对象
package com.xiao.class_based_aop.mapper;public class UserMapperImpl implements UserMapper{public void add() {System.out.println("add了一个用户");}public void delete() {System.out.println("delete了一个用户");}public void update() {System.out.println("update了一个用户");}public void select() {System.out.println("select了一个用户");}}
重点:xml中的配置
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 由 Spring容器创建该类的实例对象 --><context:annotation-config/><bean id="userMapperImpl" class="com.xiao.class_based_aop.mapper.UserMapperImpl"></bean><bean id="logAfter" class="com.xiao.class_based_aop.log.logAfter.LogAfter"></bean><bean id="logBefore" class="com.xiao.class_based_aop.log.logBefore.LogBefore"></bean><aop:config><aop:pointcut id="pointcut" expression="execution(* com.xiao.class_based_aop.mapper.UserMapperImpl.*(..))"/><aop:advisor advice-ref="logBefore" pointcut-ref="pointcut"/><aop:advisor advice-ref="logAfter" pointcut-ref="pointcut"/></aop:config></beans>
测试:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");UserMapper userMapper = context.getBean("userMapperImpl",UserMapper.class);userMapper.add();/*com.xiao.class_based_aop.mapper.UserMapperImpl的add方法开始执行......add了一个用户com.xiao.class_based_aop.mapper.UserMapperImpl的add方法执行完成*/
17.4.2基于xml实现aop
不同于基于java类实现
advice不需要实现接口,而是在xml里面完成了Aspect的全部配置,
注意:advice方法需要写在同一个类里面
接口:
package com.xiao.xml_based_aop;public interface Sell {public void sell();}
正是对象:
package com.xiao.xml_based_aop;public class SellImpl implements Sell {public void sell() {System.out.println("售卖中......");}}
advice
package com.xiao.xml_based_aop;public class Info {public void after(){System.out.println("买完了...");}private void before(){System.out.println("开始售卖");}}
xml配置
<bean id="sell" class="com.xiao.xml_based_aop.SellImpl"></bean><bean id="info" class="com.xiao.xml_based_aop.Info"></bean><aop:config><aop:aspect ref="info"><aop:pointcut id="sellInfo" expression="execution(* com.xiao.xml_based_aop.SellImpl.*(..))"/><aop:before method="before" pointcut-ref="sellInfo"></aop:before><aop:after method="after" pointcut-ref="sellInfo"></aop:after></aop:aspect></aop:config>
测试:
ApplicationContext context = new ClassPathXmlApplicationContext("aopTest.xml");Sell sell = context.getBean("sell",Sell.class);sell.sell();/*开始售卖售卖中......买完了...*/
可以发现:这样的方法是在xml里面对pointcut于advice完成的配置
而上一种基于java类的配置中advice是在类的创建时完成的,xml中配置时不需要指明advice类型
17.4.3 基于aspectj注解实现
接口
package com.xiao.annotation_based_aop;public interface HelloInterface {public void hello();}
真实对象
package com.xiao.annotation_based_aop;public class Hello implements HelloInterface {public void hello() {System.out.println("Hello");}}
advice
package com.xiao.annotation_based_aop;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspectpublic class HelloProxy {@Before("execution(* com.xiao.annotation_based_aop.Hello.*(..))")public void before(){System.out.println("你好");}@After("execution(* com.xiao.annotation_based_aop.Hello.*(..))")public void after(){System.out.println("再见");}}
配置
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration@EnableAspectJAutoProxypublic class Appconfig {@Bean("Hello")public Hello getHello(){ return new Hello(); }@Beanpublic HelloProxy getHelloProxy(){ return new HelloProxy(); }}
测试:
ApplicationContext context1 = new AnnotationConfigApplicationContext(Appconfig.class);HelloInterface hello = context1.getBean("Hello", HelloInterface.class);hello.hello();/*你好Hello再见*/
17.5基于aspectj注解实现aop的补充
17.5.1@pointcut
@pointcut是用来将joint point申明为point cut的(Pointcuts determine join points of interest and thus enable us to control when advice runs.)
下面的例子定义了一个名为anyOldTransfer的切入点,它匹配任何名为transfer的方法的执行:
@Pointcut("execution(* transfer(..))") // the pointcut expressionprivate void anyOldTransfer() {} // the pointcut signature
execution(* transfer(..))是确定point cut的关键,除了execution,还有其他的一些常用的标识符
注意:这里anyOldTranfer与pointcut没有必然关系,只是一个名字
execution:用于匹配方法执行连接点。这是使用Spring AOP时要使用的主要切入点指示符。.[主要的标识符]
within: 匹配指定的包(有两个.时包含子孙包)下面的类的方法.
this: 匹配为指定类型的代理类.
target: 匹配为hid类型的目标对象类.
The execution of any public method:
execution(public * *(..))
The execution of any method with a name that begins with set:
execution(* set*(..))
The execution of any method defined by the AccountService interface:
execution(* com.xyz.service.AccountService.*(..))
The execution of any method defined in the service package:
execution(* com.xyz.service.*.*(..))
The execution of any method defined in the service package or one of its sub-packages:
execution(* com.xyz.service..*.*(..))
Any join point (method execution only in Spring AOP) within the service package:
within(com.xyz.service.*)
Any join point (method execution only in Spring AOP) within the service package or one of its sub-packages:
within(com.xyz.service..*)
Any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:
this(com.xyz.service.AccountService)
Any join point (method execution only in Spring AOP) where the target object implements the AccountService interface:
target(com.xyz.service.AccountService)
17.5.2this()与target()的区别
官方文档笼统的概括为
- this()—->匹配代理类
- target()—->匹配目标类
经过我多次的测试,大致了解了这两者的区别:
首先要说到,Spring中代理类与目标类的区别:
- 在直接的静态代理中,真实对象与代理对象区分明确,分别属于代理类与目标类,
- 但是,在动态代理中,代理类是动态生成的,所以在代码中看起来没那么明显,
而且,Spring中有两种生成动态代理的方式:
- 基于JDK(Proxy类与InvocationHandler类)实现的【本质是基于接口】
- 基于CGLIB实现【本质是基于类】
这时我们就可以看到,不论是哪一种方法,代理类也必须实现接口[JDK]或继承类[CGLIB],
- 所以代理类也有类型,就是生成代理类时传入的类型,所以,虽然代理类时程序运行时产生的,但它的类型一定是已经在代码中的
- 这就是动态this代理中的代理类,而目标类就是真实对象所属的类。
由此,就可以知道this()与target()的用法不同了
target(com.xiao.User)———>User类与User类的子孙类都是符合的【父类不行】
target(com.xiao.UserInterface)———>实现了UserInterface的所有类都符合【父类不行】
this()与当前Spring使用的是JDK【接口】方式还是CGLIB【类】方式有关
在JDK【接口】模式下
@EnableAspectJAutoProxy(proxyTargetClass = false)或@EnableAspectJAutoProxy默认
this(com.xiao.User)直接相当于没用,因为不会有代理类是User类型
this(com.xiao.UserInterface)才能实现匹配
在CGLIB【类】模式下
@EnableAspectJAutoProxy(proxyTargetClass = true)
this(com.xiao.User)———>的代理类可以匹配
this(com.xiao.UserInterface)———>与JDK【接口】模式下的效果一致(接口也是类)
17.5.3pointcut签名(signature)的用法说明
@Pointcut("execution(public * *(..))")private void anyPublicOperation() {}@Pointcut("within(com.xyz.myapp.trading..*)")private void inTrading() {}@Pointcut("anyPublicOperation() && inTrading()")private void tradingOperation() {}
可以看出,实际上就相当于别名
@Pointcut("this(com.xiao.annotation_based_aop.HelloInterface)")public void hello() {System.out.println("Hello");}@Before("com.xiao.annotation_based_aop.Hello.hello()")public void before(){System.out.println("你好");}
就相当于:
@Before("this(com.xiao.annotation_based_aop.HelloInterface)")public void hello() {System.out.println("Hello");}
17.6配置advice
17.6.1@Before(就是前置增强)
@Before("this(com.xiao.annotation_based_aop.HelloInterface)")public void hello() {System.out.println("Hello");}
17.6.2@After(就是直接的后置增强)
@After("execution(* com.xiao.annotation_based_aop.Hello.*(..))")public void after(){System.out.println("再见");}
17.6.3@AfterReturning(获取返回后增强)
有时,我们的后置增强需要获取被增强方法的返回值,就需要使用这个
@AfterReturning(pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",returning="retVal")public void doAccessCheck(Object retVal) {// ...}
17.6.4@AfterThrowing(异常结束后增强)
@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")public void doRecoveryActions() {// ...}
类似于@AfterReturning,可以获取抛出的异常
@AfterThrowing(pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",throwing="ex")public void doRecoveryActions(DataAccessException ex) {// ...}
17.6.5@Around(环绕增强)
首先,要正确理解环绕增强是干什么的,官方文档这么说:
Around advice runs “around” a matched method’s execution. It has the opportunity to do work both before and after the method runs and to determine when, how, and even if the method actually gets to run at all.就是说,环绕增强在业务逻辑之前或之后都有机会出现,至于什么时候出现甚至出不出现由我们决定。
而决定的方式,对于@Around有不同于其他任意一种增强的特殊性,它维护了一个类型为ProceedingJoinPoint的实例,而这个实例就包含了被代理的对象实例,通过调用这个实例的==proceed()==方法执行业务代码【如果没有,直接不会执行业务代码】,@Aroud就是通过这种方法实现的。
@Around("com.xiao.annotation_based_aop.Hello.hello()")public void around(ProceedingJoinPoint pjp){System.out.println("这个类似于前置增强");try {//真实业务代码执行pjp.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("这个类似于后置增强");}
注意:使用@Around是方法的第一个参数必须是ProceedingJoinPoint类型,否则业务代码无法生效。
18.Spring与MyBatis整合
18.1整合方式
需要的包
<dependencies><!-- 测试用 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency><!-- Spring相关核心包--><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.5</version></dependency><!--Spring数据源,相当于c3p0,dbcp这些连接池,目的是实现在Spring中配置数据源而不是MyBatis中--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>3.2.13.RELEASE</version></dependency><!-- mysql连接驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.11</version></dependency><!-- myBatis相关包--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.3</version></dependency><!-- Spring的aop实现的包--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version></dependency><!--myBatis与Spring整合关键的包--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.6</version></dependency></dependencies>
接着开始尝试:
为了实现将MyBatis整合到Spring中,纠结过而言显然我们不需要去new一些MyBatis的关键对象,比如SqlSessionFactory,SqlSession,按照Spring的原则,要把这些交给Spring容器配置,我们需要给出相关的参数
Spring-MyBatis整合时IOC容器中的三个核心:
- 数据源【org.springframework.jdbc.datasource.DriverManagerDataSource】(Spring-jdbc里面的)。
- SqlSessionFactory【org.mybatis.spring.SqlSessionFactoryBean】(mybatis-spring里面的)。
- SqlSessonTemplate(实际上就是SqlSession)【org.mybatis.spring.SqlSessionTemplate】(mybatis-spring里面的)。注意:这个只能通过构造器装配(源码中没有set方法)
<!-- 数据源(原本在myBatis配置文件中配置) --><bean id="dataSources" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="url" value="jdbc:mysql:///jdbcstudy?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=UTC"/><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/><property name="username" value="root"/><property name="password" value="123456"/></bean><!--SqlSessionFactory装配--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--必要:数据源--><property name="dataSource" ref="dataSources"/><!-- 实现将mybatis配置文件与Spring配置联系起来--><property name="configLocation" value="classpath:MybatisConfig.xml"/><!-- spring中几乎可以完成原myBatis中的所有配置--><property name="mapperLocations" value="classpath:com/xiao/mapper/UserMapper.xml"/><property name="typeAliasesPackage" value="com.xiao.entity"/></bean><!-- sqlSession的装配,这里的 SqlSessionTemplate 实际上就是SqlSession --><bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"><constructor-arg index="0" ref="sqlSessionFactory"/></bean>
mapper接口与mapper.xml
package com.xiao.mapper;import com.xiao.entity.User;import java.util.List;public interface UserMapper {public List<User> selectAll();}
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--与接口类绑定--><mapper namespace="com.xiao.mapper.UserMapper"><!-- id是对应了接口类的获取查询结果的方法 resultType是对应了查询返回的对象--><select id="selectAll" resultType="user">select * from `mybatis`.`user`</select></mapper>
接着,不同于原生MyBatis中mapper只需要一个接口,在这里需要一个实现类【不然没法交给Spring管理】
package com.xiao.mapper;import com.xiao.entity.User;import org.mybatis.spring.SqlSessionTemplate;import java.util.List;public class UserMapperImpl implements UserMapper {//只需要一个开启与数据库交互的sqlSessionTamplate【等价与SqlSession】private SqlSessionTemplate sqlSession= null;public void setSqlSession(SqlSessionTemplate sqlSession) {this.sqlSession = sqlSession;}@Overridepublic List<User> selectAll() {UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> users = mapper.selectAll();return users;}}
在Spring中装配这个实体类
<bean id="userMapper" class="com.xiao.mapper.UserMapperImpl"><property name="sqlSession" ref="sqlSession"/></bean>
步骤:
- 创建Spring-Mapper.xml配置文件【基石】
- 配置数据源dataSource。【这里用了Spring的数据库连接池,也可以是dbcp,c3p0,druid等】
- 配置SqlSessionFactory(基于dataSources,可以添加额外配置或与mybatis配置文件联系)。
- 配置SqlSessionTamplate(基于SqlSessionFactory)[只能用构造器注入]。
- mapper接口与mapper.xml文件。
- mapper接口的实现类,关键属性SqlSession。
- 在Spring容器中装配mapper接口实现类。
18.2Spring申明式事务管理
官方文档:
使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的
DataSourceTransactionManager来实现事务管理。第一步:在Spring配置文件中声明事务管理
<!--要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象--><bean id = "transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSources"/></bean>
第二步:配置一个事务性advice,指明name(需要开启事务的方法名,*代表所有方法)
propagation=”REQUIRED”代表如果当前方法开启事务不管,否则开启事务【默认的,也是常用的,可以不写】。
<!--配置一个事务性advice--><tx:advice id="transaction" transaction-manager="transactionManager"><tx:attributes><tx:method name="*" propagation="REQUIRED"/></tx:attributes></tx:advice>
第三步:将这个事务性advice织入
<!--配置切面,advice类型为上面的事务性advice--><aop:config><aop:pointcut id="txPointCut" expression="execution(* com.xiao.mapper.*.*(..))"/><aop:advisor advice-ref="transaction" pointcut-ref="txPointCut"/></aop:config>
上面的三步达到的效果是:所有在com.xiao.mapper包里面的类的所有方法都开启事务
注意:
<tx:attributes><tx:method name="*" propagation="REQUIRED"/></tx:attributes>其中name的作用实际上也是pointcut的描述信息,目的是对expression指出的pointcut进行筛选,如果expression中的方法不与name匹配,实际上还是不开启事务。
