分层开发
1、表现层
- 直接和用户打交道,大部分跟界面有关(html,jsp,servlet)
2、服务层
- 指业务逻辑,业务逻辑由一个到多个基本的增删改查组成
3、 持久层(数据访问层)
- 将数据永久的保存,jdbc,mybatis
spring 框架
- 将其他框架进行整合,便于开发,提高程序的扩展性
- 声明式的事务管理
就是指不需要编写代码进行事务控制了,可以 xml 配置文件,或是用注解实现事务控制
编程式的事务管理如下:
connection.setAutoCommit(false);
connection.commit();
connection.rollback();
spring 框架的核心思想
1) IOC inversion of controll (控制反转)
servlet java 类
public class MyServlet extends HttpServlet {
service
doGet
doPost
}
// 并不需要自己创建它的实例对象, tomcat 是servlet的运行环境,由 tomcat 来创建 MyServlet 的实例对象, 负责调用 servlet 中的方法
new MyServlet();
.service// tomcat 容器
所谓的控制反转,就是把对象的一些控制权(对象的创建、一些方法的调用)都交给容器来完成。
对刚才的例子,就是把 servlet 控制权交给了 tomcat 容器
以后可以把很多对象的控制权交给 spring 容器来管理,包括对象的创建、对象的生命周期、对象的个数、对象的依赖关系2)AOP aspect oriented programming (面向切面编程)
3)OOP object oriented programming (面向对象编程)
spring 中 IOC(控制反转)
1. 添加spring依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>2. 编写spring的配置文件,提供一个 xml 的配置文件,放在 main/resources 下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"><!-- spring 的配置 如:<bean> 标签-->
</beans>3. 编写一个java 类交给spring容器管理,使用了一个 bean 标签把某个类交给spring容器管理
<!-- id 管理这个Java类的名字(随便起,不过一般是让Java类的首字母小写,UserService类 id=“userService”)class 包名.类名 管理的java类->
<bean id="userService" class="service.UserService"></bean>
4. 调用方法(以userService类为例)
1. 创建 spring 容器
类路径(对maven项目 java 和 resources) application 应用程序 context 容器,也就是放在 main/resources 下spring.xml文件写入
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");2.获取容器中的对象
- 方法一 根据 id 获取容器中的对象
类型 对象 = (类型)context.getBean(" bean 中的 id 名");
UserService service = (UserService) context.getBean("userService");//需要强制转换为你需要的对象类型,因为它返回的是object类型
- 方法二 根据 类型 获取容器中的对象
类型 对象 = context.getBean(类名.class);
UserService service = context.getBean(UserService.class);//不需要强制转换,比较方便
原来要调用方法的手段:
UserService service = new UserService();3. 使用对象
对象.方法名();
service.insert(new User());
spring 容器控制反转都能控制哪些方面
1 对象的个数
默认情况下,spring 中的每个 bean 标签只会创建一个对象(单例),可以通过配置来实现多例子<!-- 其中 prototype是多例, singleton是单例(默认值) -->
<bean scope="prototype|singleton">2 对象的生命周期方法
- 对于单例对象来讲,容器一创建,就会创建这些单例对象,并且随后就调用他们的初始化方法, 并且容器 close 时,会调用他们的销毁方法
- 而对于多例来讲,每次使用多例时,就会创建新的对象,并调用他们的初始化方法,多例对象不会调用销毁方法
- 配置初始化
<bean init-method="初始化方法名">
- 配置销毁
<bean destroy-method="销毁方法名">
3 控制懒惰初始化
默认是false(非懒惰), 改为 true 表示(懒惰)
<bean lazy-init="true">
<!-- 全局配置 -->
<beans default-lazy-init="true">4 对象之间的依赖关系
例如,管理 service 和 dao 之间的依赖关系
<bean id="userService">
<!-- property 标签用来给对象的属性赋值 name="属性名,一般与引用id的名字一样" ref="引用id" -->
<property name="userDao" ref="userDao"/>
</bean><bean id="userDao">
</bean>降低耦合度:层与层之间,要依赖接口,而不要依赖于具体实现;配合 spring 的控制反转真正实现低耦合
依赖注入
1 依赖注入的两种方式
DI (dependency inject) - 建立对象之间依赖关系的过程和方式。
把userService 需要的 userDao 注入给 userService 的属性
常见的方式:
set 方法注入,spring调用set方法来完成对属性的赋值,因此要给属性提供一个 公共的 setter 方法<property name="属性名" ref="引用id"/> //写在<bean>标签之间
构造方法注入,spring调用构造构造方法完成对属性的赋值,index是构造方法的次序,从0开始
<constructor-arg index="下标" ref="引用id"/>
2 简化注入的办法
1)autowired 自动织入 - 要么根据名字匹配, 要么根据类型匹配<!-- 根据属性名字查找容器中的bean,找到了就进行依赖注入, 可以唯一确定容器中的bean -->
<bean autowire="byName">
</bean><!-- 根据属性类型查找容器中的bean,找到了就进行依赖注入, 如果容器中有多个类型相同的bean, byType就不适用了 -->
<bean autowire="byType">
</bean>2)注解方式的注入
@Autowired 可以加在要注入的属性上,也可以加在属性对应的set方法或构造方法上,底层根据 byType 进行匹配注入配置:1、在配置好的spring的文件里的beans中写上
xmlns:context="http://www.springframework.org/schema/context"
2、在xsi:schemaLocation的双引号里加上
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
3、最后在<beans>标签之间加上,才可以启用一些注解,如:@Autowired
<context:annotation-config></context:annotation-config>
3 值注入
两种标签的方式(都是写在<bean>标签之间的):方法一 <property name="属性名" value="值" />
<!-- index="构造方法的参数下标" ref="对象id,也就是注入的接口"-->
方法二 <constructor-arg index="下标" value="值"/>
注解值注入:
方法一 @Value("值") -- 不推荐直接给值,直接写在属性上赋值
方法二 @Value("${key}") -- 引用外部配置文件(*.properties)中的值, 根据这个key 到 properties 文件中找相应的值需要在spring.xml 中配置此 properties 文件:
<context:property-placeholder location="classpath:文件的位置"> 也就是放在resource包下的 properties 文件
a.properties 文件里的内容
order.name=订单 order.price=5000
用 @Value("${key}") 注入值
import org.springframework.beans.factory.annotation.Value; public class OrderService { // @Value("${key}") 注入值 @Value("${order.price}") private int price; @Value("${order.name}") private String name; public void print() { System.out.println("name:" + name); System.out.println("price:" + price); } }
测试类
import org.springframework.context.support.ClassPathXmlApplicationContext; import service.OrderService; public class TestOrderService { public static void main(String[] args) { //获得管理容器 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); //从管理容器中获得对象 OrderService orderService = context.getBean(OrderService.class); //调用方法 orderService.print(); } }
结果
name:订单 price:5000
实际例子
1 spring 管理连接池(以阿里的 Druid 为例)
spring.xml中连接池的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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"
>
<!--destroy-method 关闭方式-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!--连接接口和数据库,以下的所有name都是规定好的,value根据需求改-->
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<!--驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--数据库用户名-->
<property name="username" value="root"/>
<!--数据库密码-->
<property name="password" value="root"/>
<!--设定连接池的最大连接数-->
<property name="maxActive" value="10"/>
<!--最小连接数-->
<property name="minIdle" value="2"/>
</bean>
</beans>
获取连接
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class TestDataSource {
public static void main(String[] args) throws SQLException {
// 没有用 spring 之前的代码
/*DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setMaxActive(10);
dataSource.setMinIdle(2);
dataSource.close()
*/
//创建 spring 容器,把classpath(resource包)下的spring.xml文件放入
ClassPathXmlApplicationContext context= new ClassPathXmlApplicationContext("spring.xml");
//获得对象
DataSource dataSource = context.getBean(DataSource.class);
//获得连接
Connection connection = dataSource.getConnection();
//输出连接
System.out.println(connection);
//查看结束时是否调用close方法
System.out.println(connection.isClosed());
}
}
结果
com.mysql.jdbc.JDBC4Connection@38bc8ab5
false
2、.在spring之前,可以使用工厂方法+接口+配置文件的方式实现解耦合
spring中的做法
<!-- 启用 @Autowired 等注解 ,在spring.xml中配置-->
<context:annotation-config></context:annotation-config>
@Autowired
private UserDao userDao; // 被动获取,依赖注入
工厂方法的做法
private UserDao userDao = UserDaoFactory.getUserDao(); // 主动获取, 一般不管依赖关系
代码如下:
创建 config.properties 文件,填写如下
dao.UserDao=dao.impl.UserDaoFile
接口
public interface UserDao {
public void insert();
public void update();
}
实现接口
public class UserDaoFile implements UserDao {
public void insert() {
System.out.println(" UserDaoDatabase 文件的实现 insert");
}
public void update() {
System.out.println(" UserDaoDatabase 文件的实现 update");
}
}
public class UserDaoDatabase implements UserDao {
public void insert() {
System.out.println(" UserDaoDatabase 数据库的实现 insert");
}
public void update() {
System.out.println(" UserDaoDatabase 数据库的实现 update");
}
}
创建工厂对象
import java.io.FileInputStream;
import java.util.Properties;
// 用户dao 的工厂类, 负责创建 UserDao对象
public class UserDaoFactory {
// 工厂方法模式 - factory-method 解耦了 service 和 dao 层
// 1. 利用了接口
// 2. 工厂方法
// 3. 把变化的部分放入配置文件
public static UserDao getUserDao() {
try {
Properties props = new Properties();
//加载config.properties文件
props.load(new FileInputStream("config.properties"));
//输出加载文件得到的键(dao.UserDao)的值(dao.impl.UserDaoFile)
System.out.println(props.get("dao.UserDao"));
//获得字符串形式的类名
String className = props.getProperty("dao.UserDao");
System.out.println(className);
// 根据字符串类名,反射得到类对象
Class c = Class.forName(className);
// 根据类对象,创建新的实例对象
UserDao userDao = (UserDao)c.newInstance();
return userDao;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
调用方法
public class UserService {
// 创建对象的职责被分给了工厂类
private UserDao userDao = UserDaoFactory.getUserDao();
public void service() {
userDao.insert();
userDao.update();
}
}
测试类
public class TestUserService {
public static void main(String[] args) {
UserService userService = new UserService();
userService.service();
}
}
结果
dao.impl.UserDaoFile
dao.impl.UserDaoFile
UserDaoDatabase 文件的实现 insert
UserDaoDatabase 文件的实现 update
只要改动 config.properties 文件里 dao.UserDao 等于号后面的值,改成 dao.UserDao=dao.impl.UserDaoDatabase name调用的就是 UserDaoDatabase 中的两个方法了,下面输出的就不是文件实现,而是数据库实现了,这样代码明显比用标签注入的多。
总结
熟练应用框架,可以使代码量变少,并且便于管理项目的各个模块,使项目分层清楚,但是框架的学习不是一天就可以搞定,需要花费时间去搞定它,尤其是它还会更新新的功能,经常去它的官网查看最新功能,可以让自己该框架更加了解。