1 Spring 框架简介及官方压缩包目录介绍
-
Spring 框架宗旨:不重新发明技术,让原有技术使用起来更加方便。
-
Spring 几大核心功能
- IoC/DI:控制反转/依赖注入;
- AOP:面向切面编程;
- 声明式事务;
-
Spring Framework runtime (系统图见下)
- test: spring 提供的测试功能;
- Core Container:是核心容器,里面的内容是 Spring 启动最基本的条件;
- Beans: Spring中负责创建类对象并管理对象;
- Core: 核心类;
- Context: 上下文参数,用于获取外部资源或者管理注解等;
- SpEl: 对应于
expression.jar
- AOP: 实现 aop 功能需要的依赖
- Aspects: 切面 AOP 依赖的包
- Data Access/Integration:Spring 封装数据访问层相关内容
- JDBC:Spring 对 JDBC 封装后的代码;
- ORM: 封装了持久层框架的代码,例如 Hibernate
- transactions:对应
spring-tx.jar
,声明式事务时使用;
- WEB:需要 Spring 完成 web 相关功能时需要;
- 例如:由 tomcat 加载 spring 配置文件时需要有 spring-web包
Servlet是具体的业务功能,不能封装
-
Spring 框架中重要概念
- 容器(Container): 将 Spring 当作一个大容器.
- 老版本中的使用 BeanFactory 接口,新版本中是 ApplicationContext 接口, 是 BeanFactory 子接口,BeanFactory 的功能在 ApplicationContext 中都有;
-
从 Spring3 开始把 Spring 框架的功能拆分成多个 jar,Spring2 及以前就一个 jar
2 IoC:控制反转(Inversion of Control)
- IoC 完成的事情:原先由程序员主动通过 new 来实例化对象事情,现在转交给 Spring 负责。
- 控制反转中控制指的是:控制类的对象;
- 控制反转中反转指的是:转交给 Spring 负责;
- IoC 最大的作用:解耦
即程序员不需要管理对象,解除了对象管理和程序员之间的耦合。
3 Spring 环境搭建
- 导入 jar,包括四个核心包一个日志包(commons-logging)(对应于核心容器)
spring-beans.jar
spring-context.jar
spring-core.jar
spring-expression.jar
commons-logging.jar
-
在 src 下新建
applicationContext.xml
文件- 上面的文件名称和路径可以自定义,这么起名称是为了记住 Spring 容器 ApplicationContext,
而applicationContext.xml
中配置的信息最终存储到了 AppliationContext 容器中。
- 上面的文件名称和路径可以自定义,这么起名称是为了记住 Spring 容器 ApplicationContext,
-
spring 配置文件(applicationContext.xml)是基于 schema(MyBatis 是基于 DTD)
- schema 文件扩展名
.xsd
- 把 schema 理解成 DTD 的升级版.(DTD是XML的语法检查器)
- schema 比 DTD 具备更好的扩展性.拓展性体现如下:
- 每次引入一个 xsd 文件是一个 namespace(即xmlns);
- 配置文件中一般只需要引入基本 schema;
配置文件的基本内容为:
- schema 文件扩展名
<?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">
</beans>
使用:
- 首先需要一个
People.java
的实体类,一般对应数据库中一个表; - 然后新建配置文件:
applicationContext.xml
- schema 通过
<bean/>
创建对象; - 默认配置文件被加载时就创建对象;
- schema 通过
<?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">
<!-- id 是获取该对象的标识,目的是为了以后方便的获取使用该对象; class 表示具体创建的是哪个类对象-->
<bean id="people" class="com.moyue.pojo.People"></bean>
</beans>
- 然后编写测试方法:Test.java,在这里可以创建对象
getBean(“<bean>标签 id 值”,返回值类型);
如果没有第二个参数, 默认是 Object;getBeanDefinitionNames()
,是Spring 容器中目前管理的所有对象;
public class SpringTest {
public static void main(String[] args) {
// 加载配置文件,同时创建对应类
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 使用 getBean() 创建对象
People people = applicationContext.getBean("people", People.class);
System.out.println("创建的对象名为:" + people + "\\n");
// 查看当前 Spring 容器中管理的所有类对象(以及数量)
int beanDefinitionCount = applicationContext.getBeanDefinitionCount();
System.out.println("当前 Spring 容器中管理的类对象数目为:" + beanDefinitionCount + "\\n");
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println( "当前 Spring 容器中管理的类对象为:" + beanDefinitionName + "\\n");
}
}
}
程序运行结果为:
创建的对象名为:People(id=0, name=null, gender=null, score=0, tel=null)
当前 Spring 容器中管理的类对象数目为:1
当前 Spring 容器中管理的类对象为:people
4 Spring 创建对象的三种方式
首先提供一个实体类:
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class People {
private Integer id;
private String name;
private String gender;
private Integer score;
private String tel;
}
方案一:通过构造方法创建
- 无参构造创建:默认情况
- 有参构造创建:需要明确配置
- 首先需要在类中提供有参构造方法
- 然后在 applicationContext.xml 中设置调用哪个构造方法创建对象
- 如果设定的条件匹配多个构造方法执行最后的构造方法,一般index、name、type有可以唯一确定对象即可,也可以多个结合使用来唯一确定
- index : 参数的索引,从 0 开始
- name: 参数名
- type:类型(区分开关键字和封装类如 int 和 Integer)
- 如果设定的条件匹配多个构造方法执行最后的构造方法,一般index、name、type有可以唯一确定对象即可,也可以多个结合使用来唯一确定
<bean id = "people" class= "com.moyue.pojo.People"></bean>
public void createObjectByConstructor(){
// 1.启动 spring 容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.从 spring 容器中取出数据
People people = (People)context.getBean("people");
}
方案二:通过实例工厂
spring只支持两种工厂:实例工厂和静态工厂
- 工厂设计模式:就是帮助创建类对象,一个工厂可以生产多个对象;
- 实例工厂:需要先创建工厂,才能生产对象
实现步骤:
- 必须要有一个实例工厂
public class PeopleFactory {
public People newInstance() {
return new People(4, "张三", "男", 98,"12323232");
}
}
以前常规的使用工厂创建对象方式:
PeopleFactory factory = new PeopleFactory();
People people = factory.newInstance();
下面是对应的在spring中使用的方式:(factory-bean 对应于 id),即在 applicationContext.xml 中配置
<!--使用实例工厂创建对象-->
<!--首先创建一个工厂-->
<bean id="factory" class="com.moyue.factory.PeopleFactory"></bean>
<!--根据工厂创建对象,factory-bean 对应工厂 id,factory-method 对应创建对象方法-->
<bean id="factoryPeople" factory-bean="factory" factory-method="newInstance"></bean>
方案三:通过静态工厂
spring 容器只负责调用静态工厂方法,而静态工厂方法内存实现需要自己完成;
- 使用静态工厂不需要创建工厂,可以快速创建对象;
实现步骤
- 首先编写一个静态工厂(在方法上添加 static)
public class StaticPeopleFactory {
public static People newInstance(){
return new People(5, "李四", "女", 98,"12323232");
}
}
- 然后在 applicationContext.xml 中
<!--使用静态工厂创建对象-->
<!--不需要创建工厂,直接创建对象,只需要指明工厂类可以直接使用工厂中的方法-->
<bean id="staticFactoryPeople" class="com.moyue.factory.StaticPeopleFactory"
factory-method="newInstance"></bean>
Spring 容器创建对象的时机
- 默认情况下:启动 spring 容器便创建对象(遇到 bean 便创建对象)
- 可以在 spring 配置文件中的
标签中设置 lazy-init 属性 - 如果lazy-init为"default/false"在启动spring容器时创建对象(默认情况)
- 如果lazy-init为"true",在执行
context.getBean
时才要创建对象
注:启动 spring 容器时候,在代码上就是执行到 :ApplicationContext context = new ClassPathXmlApplicationContext(“applicationContext.xml”);
时候;
如果配置了多个相同的 bean,则都会执行构造函数;
代码示例:
public void People(){
public People(){
System.out.println("执行构造函数");
}
}
// 分别放行执行其中一个
<bean id="people" class="com.moyue.pojo.People"></bean>
<bean id="people" lazy-init="false" class="com.moyue.pojo.People"></bean>
<bean id="people" lazy-init="true" class="com.moyue.pojo.People"></bean>
测试函数为:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
System.out.println("这是分割线");
People people = (People)context.getBean("people");
}
}
测试结果为:
// 下面是上面挨个放行之后执行的结果集
执行构造函数
这是分割线
执行构造函数
这是分割线
这是分割线
执行构造函数
补:spring 的 bean 中的 scope 配置
<!--默认值或者设置为 singleton 都表示产生的对象是单例的-->
<bean id="people" scope="singleton" class="com.moyue.pojo.People"></bean>
<!--prototype 表示多例模式-->
<bean id="people" scope="prototype" class="com.moyue.pojo.People"></bean>
测试文件
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
People people1 = (People)context.getBean("people");
People people2 = (People)context.getBean("people");
System.out.println(people1.equals(people2));
}
}
测试结果:
// 使用单例模式时,只执行一次构造函数
执行构造函数
true
// 执行多例模式时,执行两次构造函数
执行构造函数
执行构造函数
false
注:在单例模式下,启动 spring 容器,便会创建对象;在多例模式下,启动容器并不会创建对象,获得 bean 的时候才会创建对象
5 spring 的生命周期
1、spring容器创建对象
2、执行init方法
3、调用自己的方法
4、当spring容器关闭的时候执行destroy方法
注意:当scope为"prototype"时,调用 close() 方法时是不会调用 destroy 方法的
下面是测试程序:
public class SpringLifeCycle {
public SpringLifeCycle(){
System.out.println("SpringLifeCycle");
}
//定义初始化方法
public void init(){
System.out.println("init...");
}
//定义销毁方法
public void destroy(){
System.out.println("destroy...");
}
public void sayHello(){
System.out.println("say Hello...");
}
}
<bean id="springLifeCycle" init-method="init" destroy-method="destroy"
class="com.moyue.pojo.SpringLifeCycle"></bean>
测试程序:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
SpringLifeCycle springLifeCycle = (SpringLifeCycle) context.getBean("springLifeCycle");
springLifeCycle.sayHello();
// 销毁 Spring 容器
ClassPathXmlApplicationContext classContext = (ClassPathXmlApplicationContext) context;
classContext.close();
}
}
测试结果:
SpringLifeCycle
init...
say Hello...
destroy...
6 如何给 Bean 的属性赋值(注入)
就是给对象的属性进行赋值
- 方法一: 通过构造方法设置属性值;
首先 People.java 中含有两个构造方法:有参和无参
public class People{
public People(){
}
public People(int id, Student student){
this.id = id;
this.student = student;
}
}
Spring 配置环境为:
<!--
index:代表参数的位置,从 0 开始计算;
type:指的是参数的类型,在有多个构造函数的时候使用 type 进行区分,如果可以区分哪一个构造函数就可以不用写 type;
value:给基本类型赋值;
ref:给引用类型赋值;
-->
<bean id = "people" class = "com.moyue.pojo.People">
<constructor-arg index = "0", type = "java.lang.Integer" value = "1"></constructor-arg>
<constructor-arg index = "1", type = "com.moyue.pojo.Student" ref = "student"></constructor-arg>
</bean>
<bean id = "student" class = "com.moyue.pojo.Student"></bean>
测试:利用构造函数进行赋值
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
People people = (People) context.getBean("people");
System.out.println(people.getId());
总结:
1、如果spring的配置文件中的bean中没有
2、如果spring的配置文件中的bean中有
- 方法二:设置注入(本质上通过对象的 get 和 set 方法,因此一定要生成 get 和 set 方法,包括引用的对象的 get 和 set 方法;)
首先基本的数据类如下:
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class People {
private Integer id;
private String name;
private String gender;
private Integer score;
private String tel;
private List<String> list;
private Map<String, String> map;
private Set<String> set;
private String[] string;
private Properties properties;
private Student student;
}
- 如果属性是基本数据类型或 String 等简单的,首先创建People类对象,下面是给对象的属性进行赋值
标签用来描述一个类的属性,基本类型封装类、 String 等需要值的类型使用 value 赋值,引用类型使用 ref 赋值;
<bean id="people" class="com.moyue.pojo.People">
<property name="id" value="12"></property>
<property name="name" value="chenliu"></property>
<property name="gender" value="女" ></property>
<property name="score" value="13"></property>
<property name="tel" value="123243"></property>
</bean>
上面代码等效于:(一般使用上面方式)
<bean id="peo" class="com.moyue.pojo.People">
<property name="id">
<value>12</value>
</property>
<property name="name">
<value>chenliu</value>
</property>
<!-- 。。。。。。-->
</bean>
- 如果属性是
Set<?>
这里 set 里面存放的基本数据类型,如果存放的是对象,则需要将标签更换为 标签,中间的值设置为对象即可;
<property name="set">
<set>
<value>1</value>
<value>2</value>
<value>3</value>
</set>
</property>
- 如果属性是
List<?>
<property name="list">
<list>
<!--虽然这里存放 String 数据类型,但是值不使用 ""-->
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
- 如果 list 中就只有一个值,直接在里面使用 value 即可;
<property name="list" value="1">
</property>
- 如果属性是数组
当然如果数组中就只有一个值,可以直接通过 value 属性赋值
<property name="strs" >
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
- 如果属性是
map<key, value>
以及对象类型
<property name="map">
<map>
<entry key="a" value="b"></entry>
<entry key="c" value="d"></entry>
<entry key="d">
<ref bean="student"/>
</entry>
</map>
</property>
备注:引用上面的 student,需要同时配置
<bean id = student class = "com.moyue.pojo.Student"></bean>
- 如果属性是 Properties 类型,下面代码作用是对应于从 XXX.properties文件中取值
<property name="demo">
<props>
<prop key="key">value</prop>
<prop key="key1">value1</prop>
</props>
</property>
7 DI(Dependency Injection):依赖注入
spring 动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个 Connection 是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
简单来说什么是依赖注入,就是给属性赋值(包括基本数据类型和引用数据类型)
- DI 和 IoC 是一样的,当一个类(A)中需要依赖另一个类(B)对象时,把 B 赋值给 A 的过程就叫做依赖注入。
解释:就是在A中的属性是B类对象,现在要将A类中的B属性赋值;
public class A{
int id;
string name;
private B b;
}
public class B{
int score;
}
代码体现:
// 先对 A 对象进行实例化
<bean id="a" class="com.moyue.pojo.A">
<property name="b" ref="b"></property> </bean>
// 然后对 A 类中的 B 对象进行实例化
<bean id="b" class="com.moyue.pojo.B">
<property name="id" value="1"></property>
<property name="price" value="12"></property>
</bean>
8 使用 Spring 简化 MyBatis
- 需要导入 mybatis 所 有 jar 和 spring 基 本包,以及 spring-jdbc,spring-tx,spring-aop,spring-web,spring 整合 mybatis 的包等;
asm-3.3.1.jar
cglib-2.2.2.jar
commons-logging-1.1.1.jar
commons-logging-1.1.3.jar
javassist-3.17.1-GA.jar
jstl-1.2.jar
LIST.TXT
log4j-1.2.17.jar
log4j-api-2.0-rc1.jar
log4j-core-2.0-rc1.jar
mybatis-3.2.7.jar
mybatis-spring-1.2.3.jar
mysql-connector-java-5.1.30.jar
slf4j-api-1.7.5.jar
slf4j-log4j12-1.7.5.jar
spring-aop-4.1.6.RELEASE.jar
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
spring-jdbc-4.1.6.RELEASE.jar
spring-tx-4.1.6.RELEASE.jar
spring-web-4.1.6.RELEASE.jar
standard-1.1.2.jar
- 先配置 web.xml
这里为了让 Tomcat 在启动时候自动加载 Spring 的配置文件,需要在 web.xml 中告诉 Tomcat 怎么加载;
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--上下文参数,配置 Spring 配置文件位置,告诉 Tomcat 启动时候加载 Spring 配置文件路径-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- Spring 配置文件目录-->
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--内部封装了一个监听器,用于帮助加载 Spring 配置文件-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
- 然后编写 spring 配置文件,对应于实现 myBatis.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-be ans.xsd">
<!-- 数据源封装类 .数据源:获取数据库连接,spring-jdbc.jar 中(类名知道即可),代替类MyBatis中的dataSource配置功能-->
<bean id="dataSouce" class="org.springframework.jdbc.datasource.DriverMana gerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"> </property>
<property name="url" value="jdbc:mysql://localhost:3306/ssm"> </property>
<property name="username" value="root"></property>
<property name="password" value="smallming"></property>
</bean>
<!-- 创建SqlSessionFactory 对象-->
<!--这个类是专门在 Spring 中生成 sqlSessionFactory 对象的类-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据库连接信息来源于dataSource -->
<property name="dataSource" ref="dataSouce"></property>
</bean>
<!-- 扫描器相当于mybatis.xml 中mappers 下package 标签,扫描com.moyue.mapper 包后会给对应接口创建对象-->
<bean
class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 要扫描哪个包-->
<property name="basePackage" value="com.moyue.mapper"></property>
<!-- 和factory 产生关系-->
<property name="sqlSessionFactory" ref="factory"></property>
</bean>
<!-- 由spring 管理service 实现类-->
<bean id="airportService"
class="com.moyue.service.impl.AirportServiceImpl">
<property name="airportMapper" ref="airportMapper"></property>
</bean>
</beans>
- 编写代码
- 首先正常编写:pojo
- 编写 mapper 包下时,必须使用接口绑定方案或注解方案(必须有接口)
- 正常编写 Service 接口和 Service 实现类
- 需要在 Service 实现类中声明 Mapper 接口对象,并生成get/set 方法
- spring 无法管理 Servlet,在 service 中取出 Servie 对象(下面是Servlet中代码)
@WebServlet("/airport")
public class AirportServlet extends HttpServlet{
private AirportService airportService;
@Override
public void init() throws ServletException {
// 对service 实例化
// ApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xm l");
// spring 和web 整合后所有信息都存放在webApplicationContext
// 下面语句作用:因为现在在web.xml中配置spring配置文件,也就是说当Tomcat 启动后会将spring配置文件中所有东西启动,启动完成之后会把信息放在webApplicationContext容器中,下面是往外取东西
ApplicationContext ac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
airportService=ac.getBean("airportService",AirportServiceImpl.class);
}
@Override
protected void service(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException {
req.setAttribute("list", airportService.show());
req.getRequestDispatcher("index.jsp").forward(req,resp);
}
}
9 使用注解
Annotation(注解) 是 JDK1.5 及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以 @注解名
在代码中存在的。
前面的 IOC 和 DI 都是通过 xml 文件来进行配置的,我们发现 xml 配置还是比较麻烦的,那么如何简化配置呢?答案就是使用注解!
(一)注解的基本使用:@Component
实体类:People.java(属于包:com.moyue.annotation)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class People(){
private integer id;
private String name;
private String gender;
}
不使用注解的配置方式是:<bean id = "people" class = "com.moyue.pojo.People></bean>
使用注解的步骤:
- 步骤一:在 applicationContext.xml 中导入命名空间(用于约束 xml 文件格式的,下面的第一个标签为:xmls:context,表示下面的标签的格式为:
<context:标签名>
)
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
- 步骤二:引入组件扫描器
通过组件扫描,扫描所有含有注解的类:
<context:component-scan base-package = "com.moyue.annotation"></context:component-scan>
,这里使用 base-package:表示含有注解类的包名,如果需要扫描多个包,则上面的代码需要书写多行改变 base-package 里面内容即可;
- 步骤三:在 People 类中添加注解:@Component
@Component
public class People{
private Integer id;
private String name;
private String gender;
}
测试程序
@test
public void annotationTest(){
// 启动 Spring 容器;从 Spring 容器中取出数据;通过对象调用方法;
ApplicationContext context = new ClassPathXmlApplicationContext(applicationContext.xml);
People people = (People)context.getBean("people");
System.out.println(people.getId());
}
注: 如果@Component 后面没有参数,则 getBean();中使用该类的类型首字母小写即可;
即上面的等价于:
<bean id = "people" class = "com.moyue.annotation.People">
如果后面有参数,例:@Component(“dd”),相当于上面的 id 等于 dd;
(二)@Component 衍生注解
下面的三个注解是@Component 的衍生注解,功能是一样的,只是对应的层不同;
- @Repository :Dao 层
- @Service:service 层
- @Controller:web 层
(三)@Resource
@Resource 注解,可以对类成员变量、方法及其构造方法进行标注,完成自动装配工作。即可以用来消除 set、get 方法;
代码示例:
包 com.moyue.annotation 中有两个类:Student 和 People 类;
public class Student{
pubic void descStudent(){
System.out.println("执行 descStudent()");
}
}
public class People{
private Student student;
public void showStudent(){
this.student.descStudetn();
}
}
如果想要获取 People 对象,同时调用 showStudent()方法:即是给属性 student 进行实例化,即是依赖注入
- 不使用注解:
<property name = "students">
<ref bean = "student">
</property>
<bean id = "student" class = "com.moyue.annotation.Student"></bean>
- 使用注解:不需要在 applicationContext.xml 中进行配置,直接在实体类添加注解即可;只需要保证 People 中 @Resource()中 name 的值和 Student 中的@Component 的值相同;
@Compontent("people")
public class People{
@Resource(name="student")
public Student student;
public void showStudent(){
this.student.descStudent();
}
}
@Compontent("student")
public class Student{
pubic void descStudent(){
System.out.println("执行 descStudent()");
}
}
@Resource注解以后,判断该注解name的属性是否为""(name没有写)
①、如果没有写name属性,则会让属性的名称的值和spring配置文件bean中ID的值做匹配(如果没有进行配置,也和注解@Component进行匹配),如果匹配成功则赋值,如果匹配不成功,则会按照spring配置文件class类型进行匹配,如果匹配不成功,则报错
②、如果有name属性,则会按照name属性的值和spring的bean中ID进行匹配,匹配成功,则赋值,不成功则报错
(四)@Autowired 自动装配
功能和注解 @Resource 一样,可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。只不过注解@Resource 是按照名称来进行装配,而@Autowired 则是按照类型来进行装配;
- 首先创建一个接口 PeopleDao(下面所有文件放在同一个包:com.moyue.autowired)
public interface PeopleDao{
public void savePeople();
}
- 然后创建对应的实现类 PeopleDaoImpl
@Component("peopleDaoImpl")
public class PeopleDaoImpl implements PeopleDao{
@Override
public void savePeople(){
System.out.println("save People");
}
}
- 创建 PeopleService
@Service("peopleService")
public class PeopleService(){
@Autowired
private PeopleDao peopleDao;
public void savePeople(){
this.peopleDao.savePeople();
}
}
注意:这里我们在 private PeopleDao peopleDao 上面添加了注解 @Autowired,它首先会根据类型去匹配,PeopleDao 是一个接口,它的实现类是 PeopleDaoImp,那么这里的意思就是:
PeopleDao peopleDao = new PeopleDaoImp();
如果 PeopleDao 的实现类有多个呢?我们创建第一个实现类 PeopleDaoImpTwo
@Component("personDaoImplTwo")
public class PersonDaoImplTwo implements PersonDao{
@Override
public void savePerson() {
System.out.println("save Person Two");
}
}
-
方法一:更改对象名称:改为:
private PeopleDao peopleDaoImpl
,因为 @Autowired 注解默认是按照类型进行匹配,如果类型匹配有多个,则按照名称匹配,这里就是将名称改为 实现类 PeopleDaoImpl 的@Component 中的值; -
方法二:配合@Qualifier(“名称”) 使用
即在@Autowird 下面增加一行:@Qualifier("peopleDaoImpl")
,参数值为对应实现类 @Component 中的值;
在使用@Autowired时,首先在容器中查询对应类型的bean
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据
如果查询的结果不止一个,那么@Autowired会根据名称来查找。
如果查询的结果为空,那么会抛出异常。解决方法时,使用required=false
10 AOP:面向切面编程(Aspect Oriented Programming)
概念:在程序原有纵向执行流程中,针对某一个或某一些方法添加通知,形成横切面过程就叫做面向切面编程.
AOP 和 Filter 区别
两者的拦截对象不同;
-
AOP 拦截的是切点(即方法),只要该方法可以被 Spring 管理就可以被拦截;
-
Filter 拦截的是请求;
-
正常程序执行流程都是纵向执行流程(从上到下)
-
AOP 又叫面向切面编程,在原有纵向执行流程中添加横切面
-
不需要修改原有程序代码
- 体现程序的高扩展性
- 原有功能相当于释放了部分逻辑,让职责更加明确。
主要可以在程序执行的任意一个方法的前面或者后面额外添加或者扩充一些功能;
- 常用概念
- 原有功能: 称为切点,
pointcut
; - 前置通知: 在切点之前执行的功能:
beforeAdvice
- 后置通知: 在切点之后执行的功能:
afterAdvice
- 如果切点执行过程中出现异常,会触发异常通知:
throwsadvice
- 所有功能总称叫做切面;
- 织入: 把切面嵌入到原有功能的过程叫做织入
- 原有功能: 称为切点,
Spring 提供了 2 种 AOP 实现方式
方式一:Schema-based
- 每个通知都需要实现接口或类(
implement MethodBeforeAdvice
或者implement AfterReturningAdvice
) - 配置 spring 配置文件时在
<aop:config>
配置
方式二: AspectJ
- 每个通知不需要实现接口或类
- 配置 spring 配置文件是在
<aop:config>
的子标签<aop:aspect>
中配置
11 Schema-based 实现步骤
【在切点方法之前和之后分别执行了一个方法】
-
步骤一:首先导入 jar,除了 spring 核心功能包外,注意添加两个额外的包:aopalliance.jar 和 aspectjweaver.jar
-
步骤二:然后新建通知类并重写 before 或者 after 方法(通知类有两种:前置通知类和后置通知类,根据需要使用)
- 新建前置通知类
- arg0:切点方法对 Method 对象
- arg1:切点方法参数
- arg2:切点在哪个对象中
- 新建前置通知类
// 前置通知类实现 MethodBeforeAdvice 接口
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("执行前置通知");
}
}
- 新建后置通知类
- arg0:切点方法返回值
- arg1:切点方法对象
- arg2:切点方法参数
- arg3:切点方法所在类的对象
public class MyAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行后置通知");
}
}
- 步骤三:配置 spring 配置文件
- 首先引入 aop 命名空间(通过查询文档即可)
- 然后配置通知类的
<bean>
- 配置切面
*
为通配符,匹配任意方法名,任意类名,任意一级包名- 如果希望匹配任意方法参数
(..)
,就是下面 expression中的最后demo2(..)
通配符的使用范例:
<aop:pointcut expression="execution(* com.moyue.test.*.*(..))" id="mypoint"/>
表示test 这个包下面的任意类的任意方法的任意参数都需要形成切面,本质上任意位置都可以使用 * 表示任意;
下面 代码中直接配置 bean,但是方法中并没有 return 对象,能够实例化对象吗?
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--需要在上面导入 AOP 命名空间-->
<!--首先配置通知类对象,在切面中进行引入-->
<bean id="myBefore" class="com.moyue.advice.MyBeforeAdvice"></bean>
<bean id="myAfter" class="com.moyue.advice.MyAfterAdvice"></bean>
<!--然后配置切面-->
<aop:config>
<!--配置切点,里面的路径是该方法的完整路径(包括参数)-->
<aop:pointcut id="mypoint"
expression="execution(* com.moyue.pointcut.AOPpointcut.AopPointcut())"/>
<!--为切面添加通知,pointcut-ref 为切点的 id-->
<aop:advisor advice-ref="myBefore" pointcut-ref="mypoint"></aop:advisor>
<aop:advisor advice-ref="myAfter" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
<bean id = "pointcut" class="com.moyue.pointcut.AOPpointcut"></bean>
</beans>
- 步骤四:编写测试代码
public class AOPTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
AOPpointcut pointcut = applicationContext.getBean("pointcut", AOPpointcut.class);
pointcut.AopPointcut();
}
}
- 运行结果:
执行前置通知
这是切点
执行后置通知
12 配置异常通知的步骤(AspectJ 方式)
- 只有当切点报异常才能触发异常通知
- 在 Spring 中有 AspectJ 方式提供了异常通知的办法
如果希望通过 schema-base 实现,需要按照特定的要求自己编写方法。
实现步骤:
- 首先新建类,在类写任意名称的方法
public class MyThrowAdvice {
public void MyException(Exception e){
System.out.println("执行异常通知" + e.getMessage());
}
}
- 然后在 spring 配置文件中配置【配置和上面不一样,不要看错】
<aop:aspect>
的 ref 属性表示:方法在哪个类中.<aop: xxxx/>
XXX表示什么通知- method: 当触发这个通知时,调用哪个方法
- throwing: 表示异常对象名,必须和通知中方法参数名相同(可以不在通知中声明异常对象)
<!--配置异常通知-->
<bean id="exceptionAdvice" class="com.moyue.exception.MyThrowAdvice"></bean>
<aop:config>
<!-- 这里 ref 告诉 spring,这个 method 是哪一个类的,同时上面也要配 bean -->
<aop:aspect ref="exceptionAdvice">
<aop:pointcut id="mypoint"
expression="execution(* com.moyue.pointcut.AOPpointcut.AopPointcut())"/>
<!-- 这里的 method 是当告诉 spring,触发异常的时候调用的是哪一个方法,后面是针对于哪一个切点的;最后的 throwing 值为上面声明中的异常名-->
<aop:after-throwing method="MyException" pointcut-ref="mypoint"
throwing="e"></aop:after-throwing>
</aop:aspect>
</aop:config>
如果不在这里面声明通知的话,另一种方式是:
在Test类中,通过getBean获取对象,然后通过对象调用方法的时候使用 try-catch,在上面的类中使用 throws抛出,不能使用 try-catch,否则spring接收不到异常
13 异常通知(Schema-based 方式)
- 步骤一:首先新建一个类实现 throwsAdvice 接口
- 必须自己写方法,且方法名必须为 afterThrowing ,都写的话会报下面那一个
- 有两种参数方式:必须是 1 个或 4 个
- 异常类型要与切点报的异常类型一致
public class MyThrow implements ThrowsAdvice{
public void afterThrowing(Method m, Object[] args,Object target, Exception ex) {
System.out.println("执行异常通知");
}
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("执行异常通过-schema-base 方式");
}
}
- 步骤二:然后在 ApplicationContext.xml 配置
<bean id="demo" class="com.moyue.test.Demo"></bean>
<bean id="mythrow" class="com.moyue.advice.MyThrow"></bean>
<aop:config>
<aop:pointcut expression="execution(*com.moyue.test.Demo.demo1())" id="mypoint"/>
<aop:advisor advice-ref="mythrow" pointcut-ref="mypoint" />
</aop:config>
14 环绕通知(Schema-based 方式)
- 把前置通知和后置通知都写到一个通知中,组成了环绕通知
实现步骤
- 首先新建一个类实现 MethodInterceptor
public class MyArround implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("环绕-前置");
//放行,调用切点方式,语句前就相当于前置通知,语句后为后置通知
Object result = arg0.proceed();
System.out.println("环绕-后置");
return result;
}
}
- 步骤二:配置 applicationContext.xml
<bean id="demo" class="com.moyue.test.Demo"></bean>
<bean id="myarround" class="com.moyue.advice.MyArround"></bean>
<aop:config>
<aop:pointcut expression="execution(*com.moyue.test.Demo.demo1())" id="mypoint"/>
<aop:advisor advice-ref="myarround" pointcut-ref="mypoint" />
</aop:config>
15 使用 AspectJ 方式实现
- 步骤一:新建类,不用实现
public class MyAdvice {
public void mybefore(String name1,int age1){
System.out.println("前置"+name1 );
}
public void mybefore1(String name1){
System.out.println("前置:"+name1);
}
public void myaftering(){
System.out.println("后置2");
}
public void myafter(){
System.out.println("后置1");
}
public void mythrow(){
System.out.println("异常");
}
public Object myarround(ProceedingJoinPoint p) throws Throwable{
System.out.println("执行环绕");
System.out.println("环绕-前置");
Object result = p.proceed();
System.out.println("环绕后置");
return result;
}
}
- 步骤二:配置 spring 配置文件
<aop:after/>
后置通知,是否出现异常都执行<aop:after-returing/>
后置通知,只有当切点正确执行时执行<aop:after/>
和<aop:after-returing/>
和<aop:after-throwing/>
执行顺序都和在 Spring 配置文件中的配置顺序有关execution()
括号不能扩上 args- 中间使用 and 不能使用&& 由 spring 把 and 解析成&&
- args(名称) 名称自定义的.顺序和 demo1(参数,参数)对应
<aop:before/> arg-names=” 名 称 ”
名 称 来 源 于 expression=”” 中 args(),名称必须一样- args() 有几个参数,arg-names 里面必须有几个参数
arg-names=””
里面名称必须和通知方法参数名对应
<aop:config>
<aop:aspect ref="myadvice">
<!-- 这里的name1 和 age1 仅仅是对参数进行赋值,然后将这些值赋值给通知,因此上面 advice 参数名称和他们相同-->
<aop:pointcut expression="execution(* com.moyue.test.Demo.demo1(String,int)) and args(name1,age1)" id="mypoint"/>
<aop:pointcut expression="execution(* com.moyue.test.Demo.demo1(String)) and args(name1)" id="mypoint1"/>
<aop:before method="mybefore"pointcut-ref="mypoint" arg-names="name1,age1"/>
<aop:before method="mybefore1" pointcut-ref="mypoint1" arg-names="name1"/>
<aop:after method="myafter" pointcut-ref="mypoint"/>
<aop:after-returning method="myaftering" pointcutref="mypoint"/>
<aop:after-throwing method="mythrow" pointcutref="mypoint"/>
<aop:around method="myarround" pointcut-ref="mypoint"/>
</aop:aspect>
</aop:config>
16 使用注解(基于 Aspect) 实现以上功能
- spring 不会自动去寻找注解,必须告诉 spring 哪些包下的类中可能有注解
- 在spring配置文件中引入 xmlns:context命名空间
xmlns:context="http://www.springframework.org/schema/context"
xsi:http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
- @Component
- 相当于
<bean/>
- 如果没有参数,把类名首字母变小写,相当于
<bean id=””/>
- @Component(“自定义名称”)
- 相当于
实现步骤:
- 步骤一:在 spring 配置文件中设置注解在哪些包中(使用组件扫描)
<context:component-scan base-package="com.moyue.advice,com.moyue.test"></context:component-scan>
同时要添加动态代理: proxy-target-class值为 true表示使用 cglib动态代理,值为 false 表示使用 jdk 动态代理;
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
- 步骤二:在 Demo.java 类中添加@Componet,可以加参数用于别名,直接替代了bean标签
- 在方法上添加@Pointcut(“”) 定义切点(必要步骤)
@Component("dd")
public class Demo {
@Pointcut("execution(* com.moyue.test.Demo.demo1())")
public void demo1() throws Exception{
// int i = 5/0;
System.out.println("demo1");
}
}
- 步骤三:在通知类中配置MyAdvice.java 中
- @Component 类被 spring 管理
- @Aspect 相当于aop:aspect/这个标签,表示通知方法在当前类中
@Component
@Aspect // 表示该类为通知切面类
public class MyAdvice {
@Before("com.moyue.test.Demo.demo1()")
public void mybefore(){
System.out.println("前置");
}
@After("com.moyue.test.Demo.demo1()")
public void myafter(){
System.out.println("后置通知");
}
@AfterThrowing("com.moyue.test.Demo.demo1()")
public void mythrow(){
System.out.println("异常通知");
}
@Around("com.moyue.test.Demo.demo1()")
public Object myarround(ProceedingJoinPoint p) throws Throwable{
System.out.println("环绕-前置");
Object result = p.proceed();
System.out.println("环绕-后置");
return result;
}
}
下面是AOP底层原理
17 代理设计模式
-
设计模式:前人总结的一套解决特定问题的代码.
-
代理设计模式优点:
- 保护真实对象
- 让真实对象职责更明确
- 扩展
-
代理设计模式
- 真实对象(老总)
- 代理对象(秘书)
- 抽象对象(抽象功能),谈小目标
(一)静态代理设计模式
-
由代理对象代理所有真实对象的功能
- 需要自己编写代理类
- 每个代理的功能需要单独编写
-
静态代理设计模式的缺点:
- 当代理功能比较多时,代理类中方法需要写很多
(二)动态代理
-
为了解决静态代理频繁编写代理功能缺点
-
分类:
- JDK 提供的
- cglib 动态代理
JDK 动态代理
-
优点:jdk 自带,不需要额外导入 jar
-
缺点:
- 真实对象必须实现接口
- 利用反射机制,效率不高
-
使用 JDK 动态代理时可能出现下面异常
出现原因:希望把接口对象转换为具体真实对象
cglib 动态代理
-
cglib 优点:
- 基于字节码,生成真实对象的子类
- 运行效率高于 JDK 动态代理
- 不需要实现接口
-
cglib 缺点:
- 非 JDK 功能,需要额外导入 jar
-
使用 spring aop 时,只要出现 Proxy 和真实对象转换异常
设置为 true 使用 cglib
设置为 false 使用 jdk(默认值)
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
18 自动注入
- 前提:在 Spring 配置文件中对象名和 ref=”id” 即 id 名相同,可以使用自动注入,就是可以不配置
<property/>
两种配置办法
- 在
<bean>
中通过autowire=””
配置,只对这个<bean>
生效; - 在
<beans>
中通过default-autowire=””
配置,表示当前文件中所有<bean>
都生效,是全局配置内容;
参数配置
- autowire=”” 可取值如下:
- default: 默认值,根据全局
default-autowire=””
值。默认全局和局部都没有配置情况下,相当于 no; - no: 不自动注入;
- byName: 通过名称自动注入,在 Spring 容器中找类的 Id;因为在容器中找,因此就是Teacher不在配置文件中配置,使用component注解也是可以的;
- byType: 根据类型注入;
- spring 容器中不可以出现两个相同类型的
<bean>
,否则不能根据类型注入;
- spring 容器中不可以出现两个相同类型的
- constructor: 根据构造方法注入;必须在People类中提供 Teacher类的构造方法;
- 提供对应参数的构造方法(构造方法参数中包含注入对象);
- 底层使用 byName, 构造方法参数名和其他
<bean>
的 id相同;
- default: 默认值,根据全局
代码示例:
public class Teacher{
}
public class People{
private Teacher teacher;
public Teacher getTeacher(){
return teacher;
}
public void setTeacher(Teacher teacher){
this.teacher = teacher;
}
}
原来 Spring 中的配置文件写法
<bean id = "techer" class = "com.moyue.Teacher"> </bean>
<bean id = "people" class = "com.moyue.People">
<property name = "teacher" ref = "teacher">
</bean>
使用自动注解的写法:
<bean id = "techer" class = "com.moyue.Teacher"> </bean>
<!--其中 XXX 为上面的选项之一,只要能唯一确定即可-->
<bean id = "people" class = "com.moyue.People" autowire = "XXX"> </bean>
19 Spring 中加载 properties 文件
将一些配置写在 properties 属性文件中,然后使用 Spring 进行加载读取;
properties 文件中的后面的值中间不能有空格
- 步骤一:首先在 src 下新建 xxx.properties 文件
- 在 spring 配置文件中先引入 xmlns:context(具体见文档),在下面添加
如果需要记载多个配置文件逗号分割,每一个都是以classpath开头
<context:property-placeholder location="classpath:db.properties", location="classpath:abc.properties"/>
对应的 db.properties 配置示例为:
jdbc.driverClassName = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/lianxi
jdbc.username = root
jdbc.password = GJXAIOU
然后对于其中的属性值,可以spring在配置文件的 bean :id= DataSource中的value= ${key}取值
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
原来的直接在 Spring 配置的配置方式:
<bean id = "dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/lianxi"></property>
<property name="username" value="root"></property>
<property name="password" value="GJXAIOU"></property>
</bean>
- 添加了属性文件加载,并且在
中开启自动注入需要注意的地方
部分属性配置当配置全局开启自动注入之后需要进行限定:当使用 default-sutowire = "byName"
开启自动注入的时候,当同时使用 扫描器的时候;- SqlSessionFactoryBean(对象) 的 id 不能叫做 sqlSessionFactory,因为会在加载db.property的属性文件之前,就加载了其他文件,导致 ${jdbc.username}等等无法取到值
- 修改:把原来通过ref 引用替换成value 赋值,因为自动注入只能影响ref,不会影响 value 赋值
正确的配置为:
<!-- 扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.moyue.mapper"></property>
<property name="sqlSessionFactoryBeanName" value="factory"></property> </bean>
Spring 使用注解
下面被 Spring 管理的类使用注解,首先需要在 Spring 中使用
在被Spring 管理的类中通过 @Value(“${key}”)
取出properties 中内容,就不需要全部在Spring配置文件中使用 value= “${}”
进行取值;Servlet 没有被 Spring 容器管理
- 步骤一:添加注解扫描(URL 为所有包含注解的位置以及配置文件的位置):在Spring配置文件中配置
<context:property-placeholder location="classpath:second.properties"/>
<context:component-scan base-package="com.moyue.service.impl">
</context:component-scan>
- 步骤二:在类中添加
- key 和变量名可以不相同
- 变量类型任意,只要保证 key 对应的 value 能转换成这个类型就可以.
package com.moyue.service.impl
@service
public class Demo{
@Value("${my.demo}")
private String test;
// 引用对象的时候
@Resource
private UserMapper userMapper;
}
- 步骤三:配置 second.properties 文件
my.demo = 123
20 scope 属性
scope 是 <bean>
的属性,作用:控制对象有效范围(例如单例,多例等)
-
<bean/>
标签对应的对象默认是单例的;即bean声明一次之后,在类中即使使用getBean多次,但是最终对象是同一个。
无论获取多少次,都是同一个对象(单例就是多次获取某一个对象的时候,不是每次都实例化) -
scope 可取值(字符串)如下:【scope 是在 bean标签中配置】
- singleton 默认值,单例
- prototype 多例,每次获取重新实例化
- request 每次请求重新实例化(同一次请求,无论多少对象仅仅实例化一次)
- session 每个
以上是关于应用框架@Spring的主要内容,如果未能解决你的问题,请参考以下文章
使用实体框架迁移时 SQL Server 连接抛出异常 - 添加代码片段
一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式
一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式
一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式