Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由Rod Johnson在其著作 Expert One-On-One J2EE Development and Design 中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架。Spring使用基本的 JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。 Spring的 核心是控制反转(IoC)和面向切面(AOP) 。简单来说, Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架。
EE开发分成三层结构
Expert One-to-One J2EE Design and Development : J2EE 的设计和开发:(2002.EJB)Expert One-to-One J2EE Development without EJB : J2EE 不使用EJB的开发
Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring管理
Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
只需要通过配置就可以完成对事务的管理,而无需手动编程
Spring对Junit4支持,可以通过注解方便的测试Spring程序
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持
Spring对JavaEE开发中非常难用的一些API(如:JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低
Spring3.X 和 Spring4.X 和 Spring5.X
在开发中,可能会写很多的类,而有些类之间不可避免的产生依赖关系,这种依赖关系称之为耦合。
有些依赖关系是必须的,有些依赖关系可以通过优化代码来解除的。
代码示例
public class CustomerServiceImpl implements CustomerService { CustomerDao cusomerDao = new CustomerDaoImpl();}
以上的代码表示:业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种依赖关系就是我们可以通过优化代码解决的。
还有如下面的代码:
我们的类依赖了MySQL的具体驱动类,如果这时候因为某些原因数据库的品牌从MySQL改为Oracle,那么需要通过改源码来修改数据库驱动。这显然不是我们想要的。
public class JdbcDemo01{ public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); //new com.mysql.jdbc.Driver() }}
当是我们学习JDBC时,是通过反射来注册驱动的,代码如下:
Class.forName("com.mysql.jdbc.Driver");
这时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除mysql的驱动jar包,依然可以编译。但是因为没有驱动类,所以不能运行。
不过,此处也有个问题,就是我们反射类对象的全限定类名字符串是在java类中写死的,一旦要改还是要修改源码。
解决这个问题也很简单,使用配置文件配置。
在实际开发中我们可以把所有的dao和service和controller对象使用配置文件配置起来,当启动服务器应用加载的时候,通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
上面解耦的思路有2个问题:
1、存哪去?
分析:由于我们是很多对象,肯定要找个集合来存。这时候有Map和List供选择。
到底选Map还是List就看我们有没有查找需求。有查找需求,选Map。
所以我们的答案就是
在应用加载时,创建一个Map,用于存放controller,Service和dao对象。
我们把这个map称之为容器。
2、还是没解释什么是工厂?
工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。
原来:
我们在获取对象时,都是采用new的方式。是主动的。
现在:
我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的。
这种被动接收的方式获取对象的思想就是控制反转,它是spring框架的核心之一。
它的作用只有一个:削减计算机程序的耦合。
Inversion of Control:控制权的转移,创建对象的权利由应用程序转移到容器称为控制反转
削减计算机程序的耦合(解除我们代码中的依赖关系)
创建Maven项目,resources 创建spring配置文件
pom.xml依赖
<dependency> |
编写UserService类
public class UserService { |
编写applicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
|
编写测试类Test
public class Test { |
结果:
ApplicatioContext接口有两个基本的实现类:
ClassPathXmlApplicationContext : 加载类路径下的Spring配置文件。
FileSystemXmlApplicationContext : 加载本地磁盘下的Spring配置文件。
配置文件中的bean元素用于描述需要Spring容器管理的对象。其 class属性 用于指被管理对象的完整类名。
id属性 是为Spring容器管理的对象起个名字。其使用ID约束:
唯一
必须以字母开始
可以使用字母、数字、连字符、下划线、句号、冒号。但 不能出现特殊字符
name属性 也是为被Spring容器管理的对象起个名字。这样后续可以通过该名字从容器中获取对象。
没有ID中的那些约束(可以重复[不推荐]、可以出现特殊字符)
如果<bean>没有id的话,name可以当做id使用
举例
<bean id="bookAction"><bean name="/loginAction" >
|
此处的名称中由于有特殊字符,只能使用name属性。 |
结论:在定义bean的时候,推荐使用name属性 来为bean指定名称
用于声明bean在Spring容器中的作用范围。其取值包括:
singleton : 默认值 单例的;默认在Spring容器启动的时候就会创建该实例。
prototype : 多例的;默认在从Spring容器中获取bean时才会创建该实例。
request : WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中。
session : WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中。
globalSession : WEB项目中,应用在Porlet环境。如果没有Porlet环境,那么globalSession相当于session。
在bean被创建的时候,需要执行一些初始化的逻辑。可以指定bean中的一个方法为其初始化方法。这样Spring在创建完该对象之后,立即调用一下该初始化方法。
public void init() { |
<bean id="userService" class="com.tjetc.service.UserService" init-method="init" ></bean>
在bean对象销毁的时候,需要执行一些销毁逻辑。可以指定bean中的一个方法为其销毁方法。这样Spring容器在关闭之前并且销毁该对象的时候,会调用一下该销毁方法。
public void destroy() {
System.out.println("UserService.destroy().......");
}
<bean id="userService" class="com.tjetc.service.UserService" destroy-method="destroy"></bean>
注意,此处的销毁方法必须是在容器正常关闭(即执行close方法)时,才会被执行。
方式1:无参数的构造方法的方式
<bean id="userService" class="com.tjetc.service.UserService"></bean>
方式2:静态工厂实例化的方式
<!--class 配置的是工厂类,factory-method配置工厂类的静态方法,让spring调用工厂类的静态方法产生对象并管理--> <bean id="userService2" class="com.tjetc.factory.UserServiceFactory" factory-method="getBean"></bean>
public class UserServiceFactory { //静态工厂实例化的方式 public static UserService getBean(){ return new UserService(); } }
方式3:实例工厂实例化的方式
<!-- (1)配置创建UserServiceFactory2的工厂实例对象并管理 (2)配置调用UserServiceFactory2的工厂实例对象的getBean2的实例方法接收UserService对象并管理 --> <bean id="userServiceFactory2" class="com.tjetc.factory.UserServiceFactory2"></bean> <bean id="userService3" factory-bean="userServiceFactory2" factory-method="getBean2"></bean>
public class UserServiceFactory2 { /*实例方法,要调用次方法,必须先创建UserServiceFactory2的实例对象*/ public UserService getBean2() { return new UserService(); } }
定义Car
public class Car { private String name; private int price; public Car(String name, int price) { this.name = name; this.price = price; } public String getName() { return name;} public void setName(String name) { this.name = name;} public int getPrice() { return price; } public void setPrice(int price) {this.price = price;} @Override public String toString() { return "Car{" + "name='" + name + '\'' + ", price=" + price + '}'; } }
Spring配置Car
<!--spring根据Car的构造方法实例化对象--> <bean id="car" class="com.tjetc.entity.Car"> <constructor-arg name="name" value="carat"></constructor-arg> <constructor-arg name="price" value="17526"></constructor-arg> </bean>
测试
public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Car car = (Car) context.getBean("car"); System.out.println(car); } }
基本类型的属性注入
定义Car
public class Car { private String name; private int price; public Car() { } public Car(String name, int price) { this.name = name; this.price = price; } public String getName() { return name;} public void setName(String name) { this.name = name;} public int getPrice() { return price; } public void setPrice(int price) {this.price = price;} @Override public String toString() { return "Car{" + "name='" + name + '\'' + ", price=" + price + '}'; } }
Spring配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--基本类型属性注入 ,调用无参构造方法--> <bean id="car1" class="com.tjetc.entity.Car"> <property name="name" value="车车车"></property> <property name="price" value="17526"></property> </bean> </beans>
测试
public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml"); Car car = (Car) context.getBean("car1");//根据配置的id 获取car对象 System.out.println(car); } }
对象类型的属性注入
定义Person
public class Person { private String name; private Car car; public String getName() {return name; } public void setName(String name) {this.name = name;} public Car getCar() { return car;} public void setCar(Car car) {this.car = car; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", car=" + car + '}'; } }
Spring配置
<!--1、基本类型的属性注入 调用无参构造方法 --> <bean id="car1" class="com.tjetc.entity.Car"> <!--使用属性设置值前提条件:对应的类的属性要有set方法--> <property name="name" value="车车车"></property> <property name="price" value="17526"></property> </bean> <!--2、复杂(对象)类型的属性注入--> <bean id="person" class="com.tjetc.entity.Person"> <property name="name" value="kelly"></property> <!--复杂类型,使用ref来引用已经配置的bean的id值--> <property name="car" ref="car1"></property> </bean>
测试:
public class Test{ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml"); //根据配置的id 获取Person对象 Person person = (Person) context.getBean("person"); System.out.println(person); }
方案1:配置Spring的Bean PropertySourcesPlaceholderConfigurer
<!--读取properties文件--> <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> <property name="locations"> <array> <value> classpath*:db.properties </value> </array> </property> </bean>
<!--配置DbConfiguration,使用属性注入读取到的properties.文件内容--> <bean id="dbConfiguration" class="com.tjetc.common.DbConfiguration"> <!--${}读取properties配置的key对应的值数据--> <!--使用properties配置内容 格式${key} spEL--> <property name="driverName" value="${jdbc.driverName}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean>
方案2:引入 context名称空间 。使用context中提供的 <context:property-placeholder> 指定它的 location 属性值,如果有多个文件,使用 逗号 分割。
<!--读取properties文件内容--> <context:property-placeholder location="classpath*:db.properties"></context:property-placeholder>
<!--使用properties配置内容 格式${key}--> |
classpath:xx 这种写法,只会搜索到文件夹类型的下面的资源。不会搜索到jar包中的。
classpath*:xx 这种写法,都会搜索到。所以【推荐用这种】
定义CollectionDemo
package com.tjetc.entity; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; public class CollectionDemo { private String[] strs; private List<String> list; private Map<String, String> map; private Properties properties; public String[] getStrs() { return strs; } public void setStrs(String[] strs) { this.strs = strs; } public List<String> getList() { return list; } public void setList(List<String> list) { this.list = list; } public Map<String, String> getMap() { return map; } public void setMap(Map<String, String> map) { this.map = map; } public Properties getProperties() { return properties; } public void setProperties(Properties properties) { this.properties = properties; } @Override public String toString() { return "CollectionDemo{" + "strs=" + Arrays.toString(strs) + ", list=" + list + ", map=" + map + ", properties=" + properties + '}'; } }
Spring配置
<!--CollectionDemo的bean的配置--> <!--复杂类型对象注入--> <bean id="collectionDemo" class="com.tjetc.entity.CollectionDemo"> <!--数组类型的属性注入--> <property name="strs"> <array> <value>张三</value> <value>李四</value> </array> </property> <!--集合类型的属性注入--> <property name="list"> <list> <value>jack</value> <value>lucy</value> </list> </property> <!--Map类型的属性注入--> <property name="map"> <map> <entry key="username" value="aaa"></entry> <entry key="password" value="1111"></entry> </map> </property> <!--Properties的属性注入--> <property name="properties"> <props> <prop key="mm">111</prop> <prop key="nn">222</prop> </props> </property> </bean>
测试:
public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml"); CollectionDemo demo = (CollectionDemo) context.getBean("collectionDemo"); System.out.println(demo); }
随着项目越来越大,Spring的配置文件的内容也会越来越多。在实际的开发中,会将不同的配置定义在不同的xml文件中。有两种使用方式:
方式1:在创建容器的时候加载多个配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml","propertiesContext.xml");
方式2:在主配置文件中包含其它配置文件(推荐使用)
<!-- 主配置文件applicationContext.xml中引入子配置文件 --><import resource="propertiesContext.xml"></import>
propertiesContext.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--读取properties配置文件--> <!-- <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">--> <!-- <property name="locations">--> <!-- <array>--> <!-- <value>classpath*:db.properties</value>--> <!-- </array>--> <!-- </property>--> <!-- </bean>--> <!--<context:property-placeholder> 使用这个标签引入并读取properties文件--> <context:property-placeholder location="classpath*:db.properties"></context:property-placeholder> </beans>
applicationContext.xml
<!--引入其他的配置文件,读取其他配置文件内容--> <import resource="propertiesContext.xml"></import> <!--配置DbConfiguration,使用属性注入读取到的properties文件内容--> <bean id="dbConfiguration" class="com.tjetc.common.DbConfiguration"> <!--${}读取properties配置的key对应的值数据--> <property name="driverName" value="${jdbc.driverName}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean>
主要解决的问题,在XML中一个一个对bean进行配置,开发效率也不高。
目的:
使用注解要先开启注解扫描的功能
<!--配置组件的扫描com.tjetc本包及其子孙包下的所有的在类上标注有@Controller,@Service,@Repository,@Component注解的类,
spring会把标注了这些注解的类当做你配置bean节点一样纳入spring容器管理-->
<context:component-scan base-package="com.tjetc"></context:component-scan>
标注在类上,说明这是一个Spring组件。Spring中目前还提供了与 @Component 功能一致的其它三个衍生注解:
衍生的三个注解是为了让标注类本身的用途清晰,Spring在后续版本会对其增强
作用和XML配置文件的<bean>标签的实现功能一致;
使用注解注入的方式,可以不用提供set方法。
相关注解
@Value使用
package com.tjetc.common; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component("adminConstant") //小括号字符串是AdminConstant类的bean的名称 public class AdminConstant { //@Value可以把配置数据注入到成员变量或者setXXX方法,前提条件:spring要对类实例化并管理 //@Value 通过${} 读取key对应的value数据 @Value("${img.base.path}") private String basePath; @Value("111111111") private String abc; private String baseType; @Value("${img.base.type}") public void setBaseType(String baseType) { this.baseType = baseType; } public String getBasePath() { return basePath; } public void setBasePath(String basePath) { this.basePath = basePath; } public String getBaseType() { return baseType; } public String getAbc() { return abc; } public void setAbc(String abc) { this.abc = abc; } @Override public String toString() { return "AdminConstant{" + "basePath='" + basePath + '\'' + ", abc='" + abc + '\'' + ", baseType='" + baseType + '\'' + '}'; } }
applicationContext.xml配置
<!--读取properties类型的配置文件--> <context:property-placeholder location="classpath*:admin.properties"></context:property-placeholder>
测试
public class Test2 { |
@Autowoired和@Qualifier使用
public class User {
|
applicationContext.xml配置
<bean id="user1" class="com.tjetc.entity.User"> |
PrintUser.java
@Component public class PrintUser { @Autowired @Qualifier("user1") private User user;
|
测试
public class TestPrintUser { |
@Component |
测试:
public class TestPrintUser {
|
注解@Scope
@Service System.out.println("ProductServiceImpl()构造方法....."); } } |
测试
public class TestProductService { |
相关注解
@Service |
测试
public class TestProductService { |
Aop思想
Spring是解决实际开发中的一些问题:
AOP的常用应用场景
AOP最早由AOP联盟的组织提出的,制定了一套规范。Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范。
Spring的AOP底层用到两种代理机制
Joinpoint(连接点) : 所谓连接点是指 那些被拦截到的点 。在Spring中,这些点指的是方法。
Pointcut(切入点) : 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice(通知/增强) : 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知分为前置通知、后置 通知、异常通知、最终通知、环绕通知(切面要完成的功能)。
Aspect(切面) : 是切入点和通知(引介)的结合,即切入点+通知
<dependency> |
|
public class PersonService {//目标类
public void add() { System.out.println("PersonService.add()..."); } public void update() { System.out.println("PersonService.update()..."); } public void del() { System.out.println("PersonService.del()..."); throw new RuntimeException("出错了...."); } } |
<!--切面类和PersonService被spring生成实例对象并被管理起来--> <bean id="xmlTransactionPrint" class="com.tjetc.common.XmlTransactionPrint"></bean> <bean id="personService" class="com.tjetc.service.impl.PersonServiceImpl"></bean> <!--aop配置--> <aop:config> <!--切面配置--> <aop:aspect id="myaop" ref="xmlTransactionPrint"> <!--切点配置--> <!--第一个*:代表所有返回值类型,包括有返回值和void--> <!--com.tjetc.service:代表包名--> <!--包名后面的两个点:代表本包或者当前包下子孙包--> <!--第二个*:代表所有类--> <!--第三个*:代表类下所有的方法--> <!--(..):代表有0个或者1个或者多个参数--> <aop:pointcut id="mycut" expression="execution(* com.tjetc.service..*.*(..))"/> <!--通知配置--> <aop:before method="doBefore" pointcut-ref="mycut"/> <aop:after-returning method="doAfterReturning" pointcut-ref="mycut"/> <aop:after-throwing method="doAfterThrowing" pointcut-ref="mycut"/> <aop:after method="doAfter" pointcut-ref="mycut"></aop:after> <!--<aop:around method="doRound" pointcut-ref="mycut"/>-->
|
public class TestXmlAop { |
开启环绕通知配置,注释前置通知、后置通知、异常通知、最终通知,进行测试
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
applicationContext.xml配置文件用以下配置:
<!-- 开启对@Aspect注解的支持 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> |
@Aspect声明的类,和普通类一样可以添加属性和方法,还可以包含切入点、通知、引入。
Pointcut声明: 切入点声明包含两个部分:
1.签名:由一个名字和多个参数组成,必须返回void, 如:private void anyMethod(){}
2.切入点表达式 如:@Pointcut("execution(* com.tjetc.service..*.*(..))")
@Component //把该类纳入到spring容器中管理
|
@Service //纳入spring的管理 |
public class TestAnnotationAop {
|
开启环绕通知配置,注释前置通知、后置通知、异常通知、最终通知,进行测试