Spring
spring概述
Spring是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架
spring官网:http://spring.io/
Spring的发展历程
1997年IBM提出了EJB的思想
1998年,SUN制定开发标准规范EJB1.0
1999年,EJB1.1发布
2001年,EJB2.0发布
2003年,EJB2.1发布
2006年,EJB3.0发布
Rod Johnson(spring之父)
2017年9月份发布了spring的最新版本spring5.0通用版
框架的定义:拥有一整套解决方案
1、spring是一个容器,这个容器会管理很多的组件(java bean)
2、spring可以很好的解决程序间的耦合
3、spring可以集成很多第三方框架,可以充当众多技术栈之间的一个粘合剂
Spring的优势
方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合,用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于业务层上的应用
AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明方式灵活的进行事务的管理,提高开发效率和质量
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情
方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持
降低 JavaEE API 的使用难度
Spring 对 JavaEE API (如JDBC、JavaMail、远程调用等)进行了薄薄的封装,使这些API的使用难度大为降低
Java源码是经典学习范例
Spring 的源代码设计精妙,结构清晰,匠心独用,处处体现着大师对Java设计模式灵活运用以及对 Java 技术的高深造诣,它的源代码无疑是Java技术的最佳实践范例
Spring的体系结构
Core Container (spring核心容器)
为其它模块提供支持
Data Access/Integration(持久层相关)
Test单元测试模块
程序间的耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量,耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过页面传送数据的多少
模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系,数据传递关系,模块间联系越多,其耦合性越强,同时表明其独立性越差(降低耦合性,可以提高其独立性)
耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合
工厂模式解耦
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来,在接下来的使用的时候,直接拿过来用就好了
那么,这个读取配置文件,创建和获取三层对象的类就是工厂
spring中工厂的类结构图
控制反转 IoC
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度,其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)
还有一种方式叫依赖查找(Dependency Lookup),通过控制反转,对象在被创建的时候,依赖被注入到对象中
明确IoC的作用:削减计算机程序的耦合(解除代码中的依赖关系)
IOC是解耦的一种思想:解耦的途径有很多:比如依赖注入DI(只是解耦的一种解决方案)
程序中核心业务代码由主动创建对象变成被动的接收对象
★创建对象的控制权发生了转变(控制反转)
依赖注入:不在类中直接new对象,在外部需要使用本类时提供对象(可以通过工厂获取对象) (set注入,构造注入)
例如在业务层不直接提供持久层的实例
public class TestServiceImpl implements TestService {
private TestDao testDao;
//提供一个构造方法覆盖掉原有构造
public TestServiceImpl(TestDao testDao) {
this.testDao = testDao;
}
@Override
public void findData() {
testDao.findData();
}
}
★IOC
IOC — spring的IOC解决程序耦合
基于xml的方式来创建spring核心容器对象
基于XML的配置
导入配置文件约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
创建一个xml文件,导入spring核心容器相关的约束 ==> 创建核心容器对象 ==> 从容器中去取出实例
1、spring是一个容器,里面存放了很多的实例
2、可以把spring理解成一个工厂,我们需要从工厂中获取实例
3、创建一个xml文件,导入spring核心容器相关的约束,可以使用spring提供的相关标签(标签里面是存放数据的)
4、spring会解析这个xml标签中的数据
ApplicationContext 接口的实现类:
ClassPathXmlApplicationContext:
它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext:
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置
AnnotationConfigApplicationContext:
当我们使用注解配置容器对象时,需要使用此类来创建spring容器,它用来读取注解,需要提供核心配置类的字节码
在spring中,如何将一个类的实例装载到spring容器中?
基于xml的方式来创建spring核心容器对象:
bean标签的作用:将一个类的实例通过反射机制创建出来,装载进核心容器中,默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功
id属性:唯一标识,不能重复的,给对象在容器中提供唯一标识,用于获取对象
class属性:指定类的全限定类名,用于反射创建对象,默认情况下调用无参构造函数
scope属性:可以去设置该实例的作用范围
<bean id="aaa" class="com.beans.User"></bean>
从容器中取出实例
public static void main(String[] args) {
//创建核心容器对象
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("xml/beans.xml");
//要从容器中取出对应的实例
Object bean = applicationContext.getBean("aaa");
if (bean instanceof User){
User user = (User)bean;
//com.beans.User@42d80b78
System.out.println(user);
}
}
sping在获取实例时的执行流程:
会解析xml文件,去读取每一个标签,如果发现有bean标签,那么会解析bean标签对应的属性,会去读取class属性的属性值,然后通过反射机制创建实例,保存到容器中,会将id属性作为该实例的标识
问:如果将一个实例存放进核心容器中,在程序中多次获取该实例,取得的实例是不是单例的?
默认情况下是单例的,但是我们可以将其修改为多例的
在标签下修改scope属性
singleton:默认值,单例的
prototype:多例的
request:WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中
session:WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中
global session:WEB项目中,应用在Portlet环境,如果没有Porlet环境那么globalSession相当于session
<!--和默认情况相同,是单例的-->
<bean id="user" class="com.beans.User" scope="singleton"></bean>
<!--修改为多例的-->
<bean id="user" class="com.beans.User" scope="prototype"></bean>
依赖注入 Dependency Injection
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中
依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台
通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现
基于XML配置的依赖注入(set方式)
property标签的作用:
要通过set方法在xml配置文件中实现依赖注入,在类中必须实现set方法
name属性:类的属性名
value属性:类的属性值
ref属性:可以去引入容器中别的实例,属性值就是需要传入的实例的唯一标识
<bean id="user" class="com.beans.User" scope="prototype">
<!--给id属性赋值-->
<property name="id" value="1001"></property>
<property name="name" value="root"></property>
<property name="password" value="123456"></property>
</bean>
给数组赋值
<!--给数组注入数据-->
<property name="objects">
<array value-type="java.lang.String">
<value>a</value>
<value>b</value>
<value>c</value>
</array>
</property>
给自定义引用数据类型赋值
<!--注入自定义的引用数据类型-->
<property name="student" ref="student"></property>
<!--将学生实例装载进核心容器中-->
<bean id="student" class="com.beans.Student"></bean>
List
<!--给list类型的属性注入数据-->
<property name="list">
<list>
<value>123456</value>
<value>ture</value>
<value>false</value>
<value>1100</value>
</list>
</property>
Map
<!--给map类型的属性注入数据-->
<property name="map">
<map>
<entry key="key1" value="value1"></entry>
<entry key="key2" value="value2"></entry>
<entry key="key3" value="value3"></entry>
<entry key="key4" value="value4"></entry>
</map>
</property>
Date
<!--注入日期类型数据-->
<property name="date" ref="date"></property>
<!--注册一个日期实例-->
<bean id="date" class="java.util.Date"></bean>
基于XML配置的依赖注入(构造方式)
基于构造方法完成对值的注入
constructor-arg
根据有参构造来创建实例
<!--注册User实例-->
<bean id="user" class="com.os467.beans.User">
<!--通过构造方法来为属性完成注入-->
<constructor-arg name="username" value="zs001"></constructor-arg>
</bean>
多个参数构造创建实例
<!--注册User实例-->
<bean id="user" class="com.os467.beans.User">
<!--通过构造方法来为属性完成注入-->
<constructor-arg name="username" value="zs001"></constructor-arg>
<constructor-arg name="date" ref="date"></constructor-arg>
</bean>
<!--注册Date实例-->
<bean name="date" class="java.util.Date"></bean>
关于组件的生命周期
组件的作用范围是单例的:
- 组件会随着容器的销毁而销毁
init-method属性:对象在初始化时会执行的对应方法
destroy-method属性:对象在销毁时会执行的对应方法
<bean id="user" class="com.os467.beans.User" scope="singleton" init-method="initMethod" destroy-method="destroyMethod">
需要在类中定义方法
public void initMethod(){
System.out.println("初始化的方法执行了");
}
public void destroyMethod(){
System.out.println("销毁的方法执行了");
}
关闭容器对象的方法
但是要用ClassPathXmlApplicationContext去接收才能调用此方法
//关闭容器对象
applicationContext.close();
关闭后,容器中的实例会被销毁
组件的作用范围是多例的:
- 容器销毁时,组件不会销毁,会等着垃圾回收机制去回收
BeanFactory和ApplicationContext的区别
BeanFactory才是Spring容器中的顶层接口
ApplicationContext是它的子接口
创建对象的时间点不一样
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象
BeanFactory:什么使用什么时候创建对象
通过BeanFactory创建的容器,其中的实例在被用到的时候才会创建
//通过BeanFactory创建容器
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("xml/beans.xml"));
//此行执行User类实例的初始化函数
User user = beanFactory.getBean("user", User.class);
三层架构通过xml配置进行依赖注入
<!--注册表现层实例-->
<bean id="accountServlet" class="com.os467.account.servlet.AccountServlet">
<!--注入业务层的引用-->
<property name="accountService" ref="accountService"></property>
</bean>
<!--注册业务层引用-->
<bean id="accountService" class="com.os467.account.service.AccountServiceImpl">
<!--注入持久层的引用-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--注册持久层引用-->
<bean id="accountDao" class="com.os467.account.dao.AccountDaoImpl"></bean>
模拟springIOC底层基于工厂模式解耦
首先程序会去读取配置文件,然后获取文件中的全类名数据,根据反射创建对象(单例的),保存在容器里面,后面如果有用到实例的地方,我们从容器中去获取
import java.io.FileReader;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 工厂类
*/
public class BeanFactory {
//模拟一个容器
private static Map beansMap;
private static Properties properties;
//在静态代码块中去创建容器对象,为了保证存入容器中的实例是单例的,
//所有的存放实例的步骤也要在类加载的时候去完成
static {
//实例化容器
beansMap = new HashMap();
//实例化properties集合
properties = new Properties();
FileReader fileReader = null;
try {
//创建流对象
fileReader = new FileReader("src/beans");
//读取数据到集合中
properties.load(fileReader);
//获取properties集合中所有的key
Enumeration keys = properties.keys();
//遍历所有的key
while (keys.hasMoreElements()){
//先获取对应key的字符串
String key = keys.nextElement().toString();
//根据key来获取value
String propertyClass = properties.getProperty(key);
//根据反射机制创建对象
Object value = Class.forName(propertyClass).newInstance();
//将实例存到容器里
beansMap.put(key,value);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (fileReader != null){
//关闭流对象
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 定义一个获取实例的方法
*/
public static Object getBean(String beanId){
//从集合中获取实例
return beansMap.get(beanId);
}
}
通过工厂获得业务层的实例
package com.os467.factory.servlet;
import com.os467.factory.BeanFactory2;
import com.os467.factory.service.AccountService;
public class AccountServlet {
//聚合业务层引用
private AccountService accountService;
public void saveAccount() {
accountService = (AccountService) BeanFactory2.getBean("accountService");
accountService.saveAccount();
}
}
基于注解的IOC配置
注解 | 作用 |
---|---|
@Component | 把资源让spring来管理,相当于在xml中配置一个bean |
@Controller | 一般用于表现层的注解 |
@Service | 一般用于业务层的注解 |
@Repository | 一般用于持久层的注解 |
1、在配置文件中开启spring对注解的支持
2、直接在类上加对应的注解即可
在使用注解的IOC配置前需要先导入springAOP的jar包(底层需要)
配置文件约束
名称空间:xmlns:context
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
开启spring对注解的支持,其实就是一个扫描包的过程
spring程序在执行的时候会去解析配置文件的数据,然后找到对应的包结构,并且扫描该包下对应的子包,然后会去检查每一个类上面有没有spring提供的注解,如果有,会将该类的实例装载进spring核心容器中
base-package:指定的包结构
<!--开启spring对注解的支持-->
<context:component-scan base-package="com.os467"></context:component-scan>
在取出实例的时候,要注意,默认的标识是类的类名(首字符要小写)
//创建核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("xml/beans02.xml");
//取出实例
Student student = applicationContext.getBean("student", Student.class);
System.out.println(student);
类注解中的value值代表该类在获取时需要提供的id,给Student类自定义的提供一个id值
@Component("sss")
public class Student {
}
改变作用范围的注解
@Scope
指定bean 的作用范围
取值:
singleton
prototype
request
session
globalession
生命周期相关
@PostConstruct
用于指定初始化的方法
@PreDestroy
用于指定销毁的方法
public class Student {
@PostConstruct
public void initMethod(){
System.out.println("初始化的方法执行了");
}
@PreDestroy
public void destroyMethod(){
System.out.println("销毁的方法执行了");
}
}
基于注解的方式注入属性的值
用于注入数据的注解
注解 | 作用 |
---|---|
@Autowired | 根据类型注入 |
@Qualifier | 按名称注入,不能单独使用 |
@Resource | 根据id注入的 |
@Value | 用于注入基本数据类型和String类型 |
@Autowired注解
注入流程:首先spring在扫描到该属性上有Autowired注解之后,会去容器中找到该属性对应的类型与之注入
如果spring容器中与该属性类型匹配的实例有多个,那么究竟该注入哪个实例,这种情况下程序会报错
给这个类的实例起标识,起的标识一定要跟被注入属性的属性名称一致
@Qualifier注解
跟Autowired结合使用,指由Qualifier来决定究竟要注入哪个实例,需要提供注入实例的标识
Qualifier不能单独使用,只能和Autowried配合使用
@Resource注解
根据id去注入,相当于Autowired和Qualifier的结合
//指定id
@Resource(name = "accountService")
@Value注解
给属性赋值,能赋值基本数据类型和String类型
@Value("jack")
String name;
模拟springIoC底层基于注解的方式进行注入
通过设计模式+反射机制来模拟
1、spring的配置文件:beans.xml(开启扫描包的过程)spring会去扫描该包下所有子包的类,为了检查有没有spring提供的注解
2、要在指定的类上加注解,如果spring检查到该类上有注解的话,就会将该类的实例通过反射机制创建出来,装进spring中
3、检查该类中的属性上有没有spring提供的注解,如果有的话,会通过反射的方式去注入实例
修饰持久层的注解
package com.os467.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
修饰业务层的注解
package com.os467.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
修饰表现层的注解
package com.os467.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
根据属性类型注入属性实例的注解
package com.os467.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWired {
}
工厂类,用于储存实例
package com.os467.ioc.factory;
import java.util.HashMap;
import java.util.Map;
/**
* 工厂类,用于存放组件的实例(相当于容器)
*/
public class BeanFactory {
//创建Map集合对象
private static Map beansMap = new HashMap();
public static Map getBeansMap() {
return beansMap;
}
/**
* 获取实例的方法
*/
public static Object getBean(String beanId){
return beansMap.get(beanId);
}
}
模拟spring底层基于注解Ioc解耦(扫描指定包结构下的类,创建所需要的实例)
package com.os467.ioc.scan;
import com.os467.ioc.annotation.AutoWired;
import com.os467.ioc.annotation.Controller;
import com.os467.ioc.annotation.Repository;
import com.os467.ioc.annotation.Service;
import com.os467.ioc.factory.BeanFactory;
import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 模拟spring底层基于注解的Ioc解耦
*/
public class ComponentScan {
//创建一个集合对象,用于存放全类名
private static List<String> classNameList = new ArrayList();
/**
* 模拟装配实例,注入实例的过程
* @param pathName
*/
public static void getComponentScan(String pathName){
//把"."替换成"/"
pathName = pathName.replace(".","/");
//获取当前工程的绝对路径
String path = ClassLoader.getSystemResource("").getPath() + pathName;
//创建一个File对象
File file = new File(path);
//过滤指定路径下的文件
addFiles(file);
//遍历集合
for (String className : classNameList) {
try {
//创建字节码对象
Class aClass = Class.forName(className);
//根据反射检测类上有没有自定义的注解
Controller controller = (Controller)aClass.getAnnotation(Controller.class);
Service service = (Service)aClass.getAnnotation(Service.class);
Repository repository = (Repository)aClass.getAnnotation(Repository.class);
//如果类上有以上任意一个注解,我们就把该类添加到容器里面
if (controller != null || service != null || repository != null){
//根据反射创建实例
Object obj = aClass.newInstance();
//获取类的简类名
String simpleName = aClass.getSimpleName();
//将实例添加到工厂
BeanFactory.getBeansMap().put(simpleName,obj);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
//继续遍历集合,将需要注入的属性进行注入
for (String className : classNameList) {
try {
//创建字节码对象
Class aClass = Class.forName(className);
//根据反射检测类上有没有自定义的注解
Controller controller = (Controller) aClass.getAnnotation(Controller.class);
Service service = (Service) aClass.getAnnotation(Service.class);
Repository repository = (Repository) aClass.getAnnotation(Repository.class);
//如果类上有以上任意一个注解,继续检查类中的属性有没有自定义的注解
if (controller != null || service != null || repository != null) {
//获取属性字节码对象
Field[] declaredFields = aClass.getDeclaredFields();
//遍历属性数组
for (Field declaredField : declaredFields) {
//检查属性上有没有自定义的注解
AutoWired autoWired = declaredField.getAnnotation(AutoWired.class);
//如果这个注解不为空,就意味着该属性需要注入
if (autoWired != null){
//获取容器对象
Map beansMap = BeanFactory.getBeansMap();
//将容器对象转成set集合
Set<Map.Entry> entrySet = beansMap.entrySet();
//遍历set集合
for (Map.Entry entry : entrySet) {
//获取每个实例实现的接口
Class[] interfaces = entry.getValue().getClass().getInterfaces();
// 遍历接口数组,因为AutoWired是根据类型注入的,我们要保证注入的实例跟聚合的属性是同一类型
for (Class anInterface : interfaces) {
if (anInterface == declaredField.getType()){
//打破封装
declaredField.setAccessible(true);
//给属性赋值
declaredField.set(BeanFactory.getBean(aClass.getSimpleName()),entry.getValue());
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 定义一个递归的方法,用于过滤符合条件的文件
* file.listFiles():将所有的文件或者是文件夹切成一个数组
* @param file
*/
public static void addFiles(File file){
//获取该文件夹下面所有的子文件夹,FileFilter接口实现类用于返回符合条件的文件
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
//判断文件的类型
if (pathname.isDirectory()){
//继续调用递归的方法
addFiles(pathname);
}
//我们过滤的文件一般是以.class结尾的
return pathname.getPath().endsWith(".class");
}
});
//遍历文件的数组,将符合要求的文件路径切割成全类名的形式
for (File f : files) {
String path = f.getPath();
//将所有的"\"替换成"."
path = path.replace("\\",".");
//将com前面的字符串删了
path = path.substring(path.lastIndexOf("com"),path.length());
//将".class"切割掉
path = path.replace(".class","");
//将切割好的全类名添加到集合里面
classNameList.add(path);
}
}
}
基于注解的方式来创建核心容器对象
1、之前创建核心容器对象都是基于xml的,必须得有配置文件
2、spring5.0版本之后也是支持基于注解的方式创建核心容器对象,这样我们的程序就可以脱离xml配置文件
sping5.0版本后新的注解:
@Configuration
声明当前的类是一个配置类,仅起到标识作用
@ComponentScan
传入一个指定的包结构路线,开启扫描包,如果被@Component
或其它特定注解修饰的类就会被添加到核心容器中
@Bean
更多操作类似于在xml中的bean标签,用于修饰方法,可以将修饰方法的返回值作为一份实例添加到spring容器中
可以给实例起别名,默认情况下标识名称是方法名,而且修饰方法的参数实例会去容器中去匹配,匹配的规则和
@Autowired
是一样的,都是根据类型去匹配的如果在获取配置类中的实例时,有相同类型的方法,那么就会根据方法被Bean修饰的标识名称来匹配实例,或者用
@Qualifier
来修饰参数指定实例标识
注意:如果方法名称相同,即使用@Bean起了别名,有参的也会覆盖掉无参的
配置类
@Configuration
public class SpringConfig {
@Bean
public User getUser(Student student){
User user = new User();
user.setStudent(student);
return user;
}
@Bean("student")
public Student getStudent(){
return new Student();
}
}
测试类
public static void main(String[] args) {
//创建核心容器对象
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
//从容器中取实例
Object obj = applicationContext.getBean("getUser");
if (obj instanceof User){
User user = (User)obj;
}
}
@PropertySource
引入配置文件,可以通过@Value
注解读取数据
classpath: 读取的配置文件资源需要放在src文件下,可以通过el表达式取到值
@Configuration
@PropertySource("classpath:jdbc")
public class JdbcConfig {
@Value("${name}")
private String name;
@Value("${password}")
private String password;
@Bean("user")
public User getUser(){
User user = new User();
user.setName(name);
user.setPassword(password);
return user;
}
}
配置文件jdbc
name=root
password=root
@Import
可以让两个核心配置类之间存在继承的关系
引入其它核心配置类,需要提供一个配置类的字节码属性
@Import(SpringConfig.class)
★AOP
AOP概述
AOP:全称是 Aspect Oriente Programming
(面向切面编程)
作用:
在程序运行期间,不修改源码对已有方法进行增强
优势:
- 减少重复代码
- 提高开发效率
- 维护方便
Joinpoint(连接点):
连接点是指那些被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,指的就是动态代理在底层可以访问到的所有方法
Pointcut(切入点):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义,就是要对哪些连接点(方法)进行增强,即需要被增强的方法
Advice(通知/增强):
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知的类型:
前置通知:
- 调用目标方法之前执行的内容
后置通知:
- 调用目标方法之后执行的内容
异常通知:
- 在catch语句块中织入的内容
最终通知:
- 在finally语句块中织入的内容
环绕通知:
- 整个invoke方法执行的过程叫做环绕通知
Target(目标对象):
代理的目标对象
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程(增强的过程)
spring采用动态代理织入,而AspectJ采用编译器织入和类装载器织入
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面):
是切入点(要增强的方法)和通知(怎么增强)之间的关系
JDK动态代理模拟AOP底层
场景:
之前学习jdbc的时候我们接触到了事务,A账户给B账户赚钱,我们需要加入事务的操作,要满足事务的一致性
如果用代理模式来完成对事务的织入,程序该如何去写?
使用JDK动态代理来为目标类创建代理类
package com.os467.proxy;
import com.os467.account.service.AccountService;
import com.os467.utils.TransactionMangerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 为业务层做代理
*/
@Component
public class AccountProxy {
//目标对象,需要被代理的对象
@Autowired
private AccountService accountService;
/**
* 获取代理对象
* @return
*/
public Object getProxy(){
return Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
/**
* 完成对目标方法的增强
*
* 在springAop中:
*
* 前置通知:调用目标方法之前执行的内容
* 后置通知:调用目标方法之后执行的内容
* 异常通知:在catch语句块中织入的内容
* 最终通知:在finally语句块中织入的内容
* 环绕通知:整个invoke方法执行的过程叫做环绕通知
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
try {
if (method.getName().equals("transfer")){
//开启事务
TransactionMangerUtils.beginTransaction();
}
//调用目标类方法
invoke = method.invoke(accountService, args);
if (method.getName().equals("saveAccount")){
//提交事务
TransactionMangerUtils.commitTransaction();
}
}catch (Exception e){
//回滚事务
TransactionMangerUtils.rollBackTransaction();
//关闭事务
TransactionMangerUtils.closeTransaction();
e.printStackTrace();
}finally {
if (method.getName().equals("saveAccount")){
//关闭事务
TransactionMangerUtils.closeTransaction();
}
}
return invoke;
}
});
}
}
测试类
public class AopTest01 {
private static ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
public static void main(String[] args) {
//从容器中取出代理对象
AccountProxy accountProxy = applicationContext.getBean("accountProxy", AccountProxy.class);
AccountService proxy = (AccountService)accountProxy.getProxy();
proxy.transfer();
proxy.saveAccount();
}
}
基于XML的AOP配置
通过xml来配置aop(切面):我们配置的是切入点和通知之间的关系
我们想在项目中去加入收集日志的功能,但是不能在核心模块中出现,会通过aop配置切面的方式来完成对核心模块中核心方法的增强
引入约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
标签:
aop:config:
作用:用于声明开始aop的配置
aop:aspect:
作用:配置切面
aop:pointcut:
作用:配置切入点表达式
<aop:before method="beforeAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:before>
第一个*标识当前工程,后面是资源路径,类.*标识该类下的所有方法,(..)代表匹配所有无参,(String,int)表示匹配指定参数的方法
aop:before:
作用:用于配置前置通知
aop:after-returning:
作用:用于配置后置通知
配置切面
<!--配置一个目标类的实例-->
<bean id="aopMethod" class="com.os467.aop.method.AopMethod"></bean>
<!--配置通知类的实例-->
<bean id="aopLogger" class="com.os467.aop.logger.Logger"></bean>
<!--开始配置Aop-->
<aop:config>
<!--
配置切面:
要去引入一个通知类
-->
<aop:aspect id="idAspect" ref="aopLogger">
<!--
开始配置通知:
pointcut:配置切入点表达式,就是要设置通知的作用范围
-->
<!--配置前置通知-->
<aop:before method="beforeAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:before>
<!--配置后置通知-->
<aop:after-returning method="afterAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:after-returning>
<!--配置异常通知-->
<aop:after-throwing method="exceptionAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:after-throwing>
<!--最终通知-->
<aop:after method="endAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:after>
</aop:aspect>
</aop:config>
外部声明切入点表达式
<!--声明一个切入点表达式-->
<aop:pointcut id="pt01" expression="execution(* com.os467.aop.method.AopMethod.*(..))"/>
<!--配置前置通知-->
<aop:before method="beforeAdvice" pointcut-ref="pt01"></aop:before>
<!--配置后置通知-->
<aop:after-returning method="afterAdvice" pointcut-ref="pt01"></aop:after-returning>
通知类
package com.os467.aop.logger;
/**
* 日志管理类(通知类)
*/
public class Logger {
/**
* 前置通知
*/
public void beforeAdvice(){
System.out.println("前置通知开始记录日志了");
}
/**
* 后置通知
*/
public void afterAdvice(){
System.out.println("后置通知开始记录日志了");
}
/**
* 异常通知
*/
public void exceptionAdvice(){
System.out.println("异常通知开始记录日志了");
}
/**
* 最终通知
*/
public void endAdvice(){
System.out.println("最终通知开始记录日志了");
}
}
需要被增强的核心类
package com.os467.aop.method;
/**
* 需要加入收集日志功能的核心类
*/
public class AopMethod {
public void getMethod01(){
System.out.println("getMethod01方法正在执行");
}
public void getMethod02(){
System.out.println("getMethod02方法正在执行");
}
public void getMethod03(){
System.out.println("getMethod03方法正在执行");
}
}
测试类
public class AopTest {
public static void main(String[] args) {
//从容器中把目标类的实例取出来
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("xml/beans.xml");
//取出实例
AopMethod aopMethod = applicationContext.getBean("aopMethod", AopMethod.class);
//调用需要增强的方法
aopMethod.getMethod01();
}
}
基于注解的AOP配置
开启AOP对注解的支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启spring的扫描包-->
<context:component-scan base-package="com.os467"></context:component-scan>
<!--开启AOP对注解支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
@Aspect
声明当前的类是一个切面类
@Pointcut
修饰一个方法,注解中的值为切入点表达式
@Before
前置通知
@AfterReturning
后置通知
@AfterThrowing
异常通知
@After
最终通知
通过注解配置通知类
/**
* 日志管理类(通知类)
*/
@Component("logger")
@Aspect
public class Logger {
/**
* 声明一个切入点表达式
*/
@Pointcut("execution(* com.os467.aop.method.AopMethod.*(..))")
public void pt01(){};
/**
* 前置通知
*/
@Before("pt01()")
public void beforeAdvice(){
System.out.println("前置通知开始记录日志了");
}
/**
* 后置通知
*/
@AfterReturning("pt01()")
public void afterAdvice(){
System.out.println("后置通知开始记录日志了");
}
/**
* 异常通知
*/
@AfterThrowing("pt01()")
public void exceptionAdvice(){
System.out.println("异常通知开始记录日志了");
}
/**
* 最终通知
*/
@After("pt01()")
public void endAdvice(){
System.out.println("最终通知开始记录日志了");
}
}
测试类
public class AopTest02 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("xml/beans02.xml");
AopMethod aopMethod = applicationContext.getBean("aopMethod", AopMethod.class);
aopMethod.getMethod01();
}
}
环绕通知
在配置环绕通知的时候可以不去配置前置,后置,异常,结束这些通知,因为可以在环绕通知中定义这些通知
xml配置环绕通知
<!--配置环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pt01"></aop:around>
注解配置环绕通知
@Around("pt01()")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){环绕通知具体内容}
增强方法内容
/**
* 环绕通知,需要给它一个接口参数,spring会自动提供实例
*/
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
//获取调用目标方法时所对应的参数
Object[] args = proceedingJoinPoint.getArgs();
//调用目标方法
try {
System.out.println("前置通知");
//类似于invoke方法
proceedingJoinPoint.proceed(args);
System.out.println("后置通知");
} catch (Throwable throwable) {
System.out.println("异常通知");
throwable.printStackTrace();
}finally {
System.out.println("结束通知");
}
}
单元测试junit
单元测试在底层是封装了main函数的,在不写主函数的情况下,可以去让程序跑起来
通过spring来集成单元测试(可以在不创建核心容器的情况下,取到容器中的实例)
@Test
在方法上加上Test注解,该方法就会变成像主函数一样可以运行
@Test
public void test01(){
System.out.println("成功运行了该方法");
}
在程序运行的时候动态的获取核心容器对象
@RunWith
这个注解可以替换运行器,我们选用spring提供的运行器
@ContextConfiguration
指定如何创建核心容器对象,需要提供xml路径或者是配置类class
/**
* @RunWith 这个注解可以替换运行器
* SpringJUnit4ClassRunner 这个类实现了运行器接口,可以在spring程序运行的时候获取核心容器对象
* @ContextConfiguration 以什么方式创建核心容器对象(xml/注解)
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:xml/beans.xml")
public class JunitTest01 {
@Autowired
private ApplicationContext applicationContext;
@Test
public void test01(){
//取出实例
AccountServiceImpl service = applicationContext.getBean("service", AccountServiceImpl.class);
service.saveAccount();
}
}
基于核心配置类创建容器(注解注册)
@ContextConfiguration(classes = SpringConfig.class)
jdbcTemplate模板对象
jdbcTemplate对JDBC做了封装,相当于是一个工具类
//创建jdbcTemplate模板对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//创建数据源的示例
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
//设置连接地址
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/web_test?serverTimezone=GMT&characterEncoding=utf-8");
//设置驱动全类名
driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//设置用户名
driverManagerDataSource.setUsername("root");
//设置密码
driverManagerDataSource.setPassword("root");
//给模板对象注入数据源的实例
jdbcTemplate.setDataSource(driverManagerDataSource);
//执行一个添加的功能
int num = jdbcTemplate.update("insert into tb_user(username,password)values('tom','10010011')");
System.out.println(num == 1 ?"添加成功":"添加失败");
xml中的特殊符号
表达式 | 符号 |
---|---|
< | < |
> | > |
& | & |
' | ' |
" | " |
通过xml配置注入jdbcTemplate模板实例
<?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"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启对注解的支持-->
<context:component-scan base-package="com.os467"></context:component-scan>
<!--配置一个jdbcTemplate实例-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入数据源的实例-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--注入一个数据源的实例-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--注入源数据-->
<property name="url" value="jdbc:mysql://localhost:3306/web_test?serverTimezone=GMT&characterEncoding=utf-8"></property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
junit测试类
package com.os467.test;
import com.os467.account.service.AccountService;
import com.os467.account.service.AccountServiceImpl;
import com.os467.beans.User;
import com.os467.config.SpringConfig;
import com.os467.user_test.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* @RunWith 这个注解可以替换运行器
* SpringJUnit4ClassRunner 这个类实现了运行器接口,可以在spring程序运行的时候获取核心容器对象
* @ContextConfiguration 以什么方式创建核心容器对象(xml/注解)
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:xml/beans.xml")
public class JunitTest01 {
@Autowired
private AccountService accountService;
@Autowired
private UserService userService;
@Test
public void test01(){
accountService.saveAccount();
}
/**
* 通过jdbcTemplate模板连接数据库
*/
@Test
/**
* 测试添加的功能
*/
public void test02(){
int i = userService.addUser();
System.out.println(i == 1 ?"添加成功":"添加失败");
}
/**
* 测试删除功能
*/
@Test
public void test03(){
int i = userService.deleteUserById();
System.out.println(i == 1 ?"删除成功":"删除失败");
}
/**
* 测试修改功能
*/
@Test
public void test04(){
int i = userService.updateUserById();
System.out.println(i == 1 ?"修改成功":"修改失败");
}
/**
* 测试查询的功能
*/
@Test
public void test05(){
List<User> userList = userService.findAllByUsers();
for (User user : userList) {
System.out.println(user);
}
}
}
持久层
package com.os467.user_test.dao;
import com.os467.beans.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<User> findAllByUsers() {
return jdbcTemplate.query("select * from tb_user", new RowMapper<User>() {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
//对java对象的封装,创建User实例
User user = new User(id, username, password);
return user;
}
});
}
@Override
public int deleteUserById() {
return jdbcTemplate.update("delete from tb_user where id = 7");
}
/**
* 修改
* @return
*/
@Override
public int updateUserById() {
return jdbcTemplate.update("update tb_user set username = ?,password = ? where id = ?","tom","123",6);
}
@Override
public int addUser() {
return jdbcTemplate.update("insert into tb_user(username,password)values('tom','111')");
}
}
Spring中的事务控制
JavaEE 体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案
spring框架为我们提供了一组事务控制的接口,具体在后面的第二小节介绍,这组接口是在spring-tx-5.0.2.RELEASE.jar中
spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现,我们学习的重点是使用配置的方式实现
PlatformTransactionManager接口
此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法
管理事务的子类对象:
1**.DataSourceTransactionManager使用SpringJDBC或iBatis**进行持久化数据时使用
2.HibernateTransactionManager使用Hibernate版本进行持久化数据时使用
基于XML的声明式事务控制
配置步骤:
1、配置事务管理器
2、配置事务的通知引用事务管理器
3、配置事务的属性
4、配置AOP切入点表达式
5、配置切入点表达式和事务通知的对应关系
引入xmlns:tx相关约束
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
<!--配置一个事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源实例-->
<property name="dataSource" ref="dataSource"></property>
</bean>
场景:银行转账的场景
TransactionDefinition事务属性
isolation:
获取事务的隔离级别
默认值是DEFAULT,数据库级别的
propagation:
获取事务的传播行为,表示是否开启事务,以什么样的策略开启
REQUIRED表示一定会开启事务,无论是增删改查都会开启
SUPPORTS表示有事务就会开启事务,没有事务就不会开启,会在增删改 的场景下开启事务,查询场景不会开启
timeout:
获取超时时间,默认值是-1,永不超时,如果是正数的话,可以以秒为单 位设置超时时间
read-only:
会影响事务是否开启
获取是否是只读事务(true/false),默认值是false,在查询的业务场景下,会把该属性设置成true
rollback-for:
是否开启回滚,默认值是true 自定义一个异常,除了该异常回滚,所有异常都不回滚
no-rollback-for:
自定义一个异常,除了该异常不回滚,所有异常都回滚
默认情况下,所有异常都回滚
只有在增删改的业务场景下,才会取开启事务,查询业务场景下不用开启事务,或者是只读就行
注意:spring中单个事务的生命周期只发生在在一个方法中(需要被AOP增强的每个方法)
如何配置事务管理器
<!--配置一个事务管理器-->
<bean id="dataSourceManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源实例-->
<property name="dataSource" ref="dataSource"></property>
</bean>
如何配置事务的通知
<!--配置一个事务的通知-->
<tx:advice transaction-manager="dataSourceManager">
<!--配置事务的属性-->
<tx:attributes>
<!--
<tx:method name=""/>
name属性:用于匹配需要织入事务的方法
设置匹配规则,匹配方法名称,*代表通配符
isolation:事务的隔离级别
-->
<tx:method name="update*" isolation="DEFAULT" />
</tx:attributes>
</tx:advice>
完整的spring事务织入xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启对注解的支持-->
<context:component-scan base-package="com.os467"></context:component-scan>
<!--配置一个jdbcTemplate实例-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入数据源的实例-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--注入一个数据源的实例-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--注入源数据-->
<property name="url" value="jdbc:mysql://localhost:3306/web_test?serverTimezone=GMT&characterEncoding=utf-8"></property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
基于注解的声明式事务控制
配置步骤:
1、配置事务管理器并注入数据源
2、在业务层使用@Transactional注解
3、在配置文件中开启spring对注解事务的支持
@Transactional
事务注解,可以修饰类,也可以修饰方法
修饰类:该类下所有的方法都会加入事务的支持
修饰方法:只有指定方法会加入事务支持,方法的优先级比类要高
注意:使用注解,事务管理器还需要在xml中配置,但是事务通知可以不用配置了
package com.os467.account.service;
import com.os467.account.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Transactional(propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void saveAccount() {
accountDao.savAccount();
}
}
★MVC
什么是MVC?
MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范
是将业务逻辑、数据、显示分离的方法来组织代码
MVC主要作用是降低了视图与业务逻辑间的双向耦合
MVC不是一种设计模式,MVC是一种架构模式,不同的MVC存在差异
Model(模型):数据模型,提供前端要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao)和服务层(行为Service),也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务
View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西
Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示,也就是说控制器做了个调度员的工作
SpringMVC
Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架
Spring MVC的特点:
1、轻量级,简单易学
2、高效,基于请求响应的MVC框架
3、与Spring兼容性好,无缝结合
4、约定优于配置
5、功能强大:RESTful、数据验证、格式化、本地化、主题化等
6、、简洁灵活
最开始我们在处理请求和响应是基于servlet的,那servlet是一个规范,不属于框架,springMVC是一个表现层的框架,在底层封装了servlet
springMVC核心原理
- 用户发送请求给服务器,url:user.do
- 服务器收到请求,发现DispatcherServlet可以处理,于是调用DispatcherServlet
- DispatcherServlet内部,通过HandlerMappings(处理映射器)检查这个url有没有对应的Controller,如果有,则通HandlerAdapters(处理适配器)调用Controller
- Controller开始执行
- Controller执行完毕后,如果返回字符串,则ViewResolvers将字符串转化成相应的视图对象,如果返回ModelAndView对象,该对象本身就包含了视图对象信息
- DispatcherServlet通过ViewResolvers(视图解析器)将视图对象中的数据输出给服务器
- 服务器将数据输出给客户端
springMVC整体调度流程
- 客户端发送请求给服务端,springMVC框架会通过DispatcherServlet来做一个资源的拦截(http://localhost:8080/servlet/test)
- DispatcherServlet会调用处理映射器HandlerMappings来完成对地址的解析,然后把解析的信息告诉DispacherServlet
- DispacherServlet调用处理适配器HandlerAdapters从容器中找到对应的资源(会找到controller1对应的资源)
- HandlerAdapters找到对应资源之后,会去执行具体的Controller(然后会去封装数据,设置视图名称)
- 执行完逻辑之后会将视图和模块(ModelAndView)返回给DispacherServlet
- 继续调用视图解析器ViewResolvers视图解析器来解析ModelAndView里面视图名称,然后根据视图解析器内部的匹配规则来定位到具体的前端资源(jsp)
- ViewResolvers在解析完之后会将解析的信息告诉DispacherServlet,然后会去做具体的页面跳转,数据渲染等工作
配置springMVC
springMVC框架如何去使用,如何去配置?
使用控制器
创建一个TestController类,实现Controller接口
package com.os467.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
//创建一个模型视图对象
ModelAndView modelAndView = new ModelAndView();
//封装数据
modelAndView.addObject("name","Hello spring MVC");
//设置视图名称
modelAndView.setViewName("test02");
return modelAndView;
}
}
我们在定义了一个Controller之后如何通过mvc去访问这个资源?
1、在web.xml中配置DispatcherServlet:mvc框架前端总调度器
具体会去完成web交互时的各自调度工作
- Spring的web框架围绕DispatcherServlet调度Servlet设计
- DispatcherServlet的作用是将请求分发到不同的处理器,从Spring2.5开始,使用Java5或者以上版本的用户可以采用基于注解形式进行开发
在web.xml中配置前端总调度器,所有的访问请求都会进入此资源
<?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">
<!--配置前端总调度器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--拦截所有的客户端的请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2、由于MVC框架属于spring的功能模块,所以必须得有核心容器的支持,要去创建spring配置文件springMVC.xml
创建springMVC.xml配置文件,引入springMVC的xml约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
</beans>
在web.xml中注册配置文件位置,核心容器对象可以交给DispacherServlet去创建,需要指定配置文件的位置,以及设置启动级别
<!--配置前端总调度器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--绑定springMVC的配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--配置文件的路径-->
<param-value>classpath:xml/springMVC.xml</param-value>
</init-param>
<!--设置启动级别,需要在tomcat服务器启动的时候就去加载核心容器对象-->
<load-on-startup>1</load-on-startup>
</servlet>
继续配置springMVC.xml,注册controller组件,视图解析器实例
<!--注册controller组件-->
<bean id="/controller1" class="com.os467.controller.TestController"></bean>
<!--处理映射器和适配器可以不做注册,这里仅作演示-->
<!--注册一个处理映射器-->
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!--注册一个处理适配器-->
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!--必须要配置视图解析器-->
<!--注册一个视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--设置视图的前缀-->
<property name="prefix" value="/jsp/"></property>
<!--设置视图的后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
SpringMVC基于注解
<mvc:default-servlet-handler/>
让SpringMVC不处理静态资源
<mvc:annotation-driven/>
支持mvc注解驱动
InternalResourceViewResolver
视图解析器
配置xml,开启对注解的支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--开启扫描包-->
<context:component-scan base-package="com.os467"></context:component-scan>
<!--过滤静态资源-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!--支持mvc注解的驱动-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--配置视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--设置前缀-->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!--设置后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
@Controller注解
这个注解如果只是在spring框架中使用的话,就是将当前类的实例存到容器中
但是如果在springMVC中去使用,它不仅可以存实例到容器,还能够将该类下任意一个方法的返回值是String
的方法,返回值会作为视图名称,用于视图跳转
@RequestMapping注解
用于地址的映射,可以修饰类也可以修饰方法,也可以只在方法上使用,能够体现层级关系
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping("test")
public String getTest(){
return "test";
}
}
此时在前端访问:http://localhost:8080/springMVC/user/test 测试资源
@ResponseBody注解
让当前视图失效,并且会直接返回数据
springMVC如何去接收前端传递过来的值
springMVC可以支持参数接值,你在前端需要传递的数据,后端mvc可以使用参数进行接收
可以在接收参数的时候接收HttpServletrequest,再接收Parameter值
也可以在参数中根据对应参数名接值
//前端测试:http://localhost:8080/springMVC/user/test03?username=jack001&password=123456&i=10&d=3.14
@RequestMapping("test03")
public String getTest03(String username,String password,int i,double d){
System.out.println(username+" "+password+" "+i+" "+d);
return "test";
}
springMVC在参数列表中可以通过实体进行接值
mvc会根据接收实例的属性名称来接收对应的前端数据,并且封装成java对象
@RequestMapping("test04")
public String getTest04(MvcVo mvcVo){
System.out.println(mvcVo);
return "test";
}
springMVC如何将封装好的数据存到Model中
在参数中接收一个Model接口的实例,是由springMVC提供的,底层封装了request域
通过返回字符串的方式
@RequestMapping("test05")
public String getTest05(MvcVo mvcVo, Model model){
//把实体存到Model中,底层封装了一个request域
model.addAttribute("mcvVo",mvcVo);
return "test02";
}
通过返回ModelAndView的方式
@RequestMapping("list")
public ModelAndView getEmpList(){
ModelAndView modelAndView = new ModelAndView();
List<Emp> empList = empService.getEmpList();
//访问持久层查询数据
modelAndView.addObject("empList",empList);
//设置视图名称
modelAndView.setViewName("list");
return modelAndView;
}
通过SpringMVC来实现转发和重定向
forward
redirect
项目
通过spring + springMVC + jdbcTemplate 来搭建一个web项目,完成员工列表
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com