Spring框架
Spring简介
spring框架是由于软件开发的复杂性而创建的轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
它使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,其用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从spring中受益。
Spring优势
Spring具有简单、可测试和松耦合等特点,从这个角度出发,Spring不仅可以用于服务器端开发,也可以应用于任何Java应用的开发中。
1.方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2.AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
3.声明事物的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
4.方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
5.方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
6.降低Java EE API的使用难度
Spring对很多难用的Java EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。
7.Java 源码是经典学习范例
Spring的源码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。Spring框架源码无疑是Java技术的最佳实践范例。如果想在短时间内迅速提高自己的Java技术水平和应用开发水平,学习和研究Spring源码将会使你收到意想不到的效果
Spring缺点
- 中断了应用程序的逻辑,使代码变得不完整,不直观。此时单从Source无法完全把握应用的所有行为。
- 将原本应该代码化的逻辑配置化,增加了出错的机会以及额外的负担。
- 时光倒退,失去了IDE的支持。在目前IDE功能日益强大的时代,以往代码重构等让人头痛的举动越来越容易。而且IDE还提供了诸多强大的辅助功能,使得编程的门槛降低很多。通常来说,维护代码要比维护配置文件,或者配置文件+代码的混合体要容易的多。
- 调试阶段不直观,后期的bug对应阶段,不容易判断问题所在。
- spring像一个胶水,将框架黏在一起,后面拆分的话就不容易拆分了
Spring的开发步骤
开发步骤如下图
&emsp**;1.导入坐标、在pom.xml文件中导入Spring开发的基本包坐标**
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
2.编写Dao接口和实现类(创建Bean)
Dao接口:interface 我们在这里写一个save方法
package com.itheima.dao;
public interface UserDao {
public void save();
}
Dao接口的实现类、调用Dao接口的save方法并输出一句话
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save running...");
}
}
3.创建Spring核心配置文件(applicationContext.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>
4.在Spring配置文件中配置UserDaoImpl
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
5.使用Spring的API获得Bean实列
package com.itheima.demo;
import com.itheima.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserDaoDemo {
public static void main(String[] args) {
// 获取配置文件,调用配置文件里面的方法
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// 根据Spring生成的唯一id调用UserDao的实现方法
UserDao userDao = (UserDao) app.getBean("userDao");
userDao.save();
}
}
运行结果如下
Spring配置
Bean标签范围配置
1.Singleton
在加载配置文件,加载Spring容器的时候创建Bean、此标签代表创建的Bean的id时唯一的
标签的配置如下
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton"></bean>
测试代码
@Test
public void test1(){
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao1 = (UserDao) app.getBean("userDao");
UserDao userDao2 = (UserDao) app.getBean("userDao");
System.out.println(userDao1);
System.out.println(userDao2);
}
}
测试结果
2.prototype
在调用getBean();方法时创建Bean
配置如下
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype"></bean>
测试代码同上一个方法、测试结果如下
说明在当前Spring容器中UserDao的Bean存在多个
Bean的生命周期配置
int-method:指定类中初始化方法名称
destory-method:指定类中销毁方法名称
在UserDao接口的实现类中写如两个方法
public class UserDaoImpl implements UserDao {
public UserDaoImpl() {
System.out.println("UserDaoImpl创建....");
}
// 设置一个初始化方法
public void init(){
System.out.println("初始化方法.....");
}
// 设置一个销毁方法
public void destory(){
System.out.println("销毁方法.....");
}
配置如下:
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"
init-method="init" destroy-method="destory"></bean>
测试代码
public class SpringTest {
@Test
public void test1(){
ClassPathXmlApplicationContext app= new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao1 = (UserDao) app.getBean("userDao");
System.out.println(userDao1);
app.close();
}
}
注释: 手动关闭(app.close(); ),在测试过程中如果不手动关闭的话,程序可能销毁过快导致销毁方法来不及打印,所以选择手动关闭给他反应时间
运行结果如下
Spring Bean实例化的三种方式
Bean的依赖注入
创建业务层的UserService方法和对应的实现类,将UserDao注入到UserService类内部
两种方法 构造方法 set方法
1.set方法
首先在service实现类(implement)中定义一个userDao
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
然后在Spring容器中配置织入注入方法
将容器中的userDao通过userService中的set方法注入给service
配置如下
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" ></bean>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
// 使用property注入Bean的依赖
<property name="userDao" ref="userDao"></property>
注使用property注入Bean的依赖时得在UserService的实现类中获取定义userDao的set方法
// 获取上面定义的userDao的set方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
set方法注入还有一个p命名空间注入 比较方便
用p:userDao-ref="userDao"
注入
p命名空间引入
<beans xmlns="http://www.springframework.org/schema/beans"
// 引入p命名空间
xmlns:p="http://www.springframework.org/schema/p"
p命名空间方法的依赖注入
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype"></bean>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"
p:userDao-ref="userDao">
// <!-- <property name="userDao" ref="userDao"></property>-->
</bean>
2.构造方法注入(有参构造)
在实现类中写入userDao的无参和有参方法
// 定义一个私有的userDao
private UserDao userDao;
// userDao的有参构造方法
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
// userDao的无参构造方法
public UserServiceImpl() {
}
// 获取userDao的set放噶
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
xml内配置
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype"></bean>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
测试代码如下
package com.itheima.demo;
import com.itheima.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserDaoDemo {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) app.getBean("userDao");
userDao.save();
System.out.println(userDao);
}
}
运行结果
普通数据类型集合等的注入
注入数据的三种数据类型
1.普通数据类型的注入
在UserDao接口的实现类内写入普通数据、并通过实现类里的save方法输出数据
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
import com.itheima.domain.User;
public class UserDaoImpl implements UserDao {
private String username;
private int age;
// 获取写入数据的set方法
public void setUsername(String username) {
this.username = username;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void save() {
// 输出数据
System.out.println(username + "-----"+ age);
System.out.println("save running...");
}
}
配置文件、将数据注入Spring容器中
使用<property name=" " value=" " 引入数据
name代表我在UserDao实现类写入的数据名称
value代表给我的数据注入属性、参数
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype">
<property name="username" value="zhangsan"/>
<property name="age" value="18"/>
</bean>
运行结果
2.集合数据类型
这里学习三种 List、Map、以及Properties
在实现类中写入这三个集合并生成他们的set方法
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
import com.itheima.domain.User;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class UserDaoImpl implements UserDao {
private List<String> strList;
private Map<String, User> userMap;
private Properties properties;
public void setStrList(List<String> strList) {
this.strList = strList;
}
public void setUserMap(Map<String, User> userMap) {
this.userMap = userMap;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public void save() {
System.out.println(strList);
System.out.println(userMap);
System.out.println(properties);
System.out.println("save running...");
}
}
配置文件注入集合方法
List集合配置
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype">
<property name="strList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property></bean>
Map集合配置
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype">
<property name="userMap">
<map>
<entry key="u1" value-ref="user1"></entry>
<entry key="u2" value-ref="user1"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">ppp</prop>
<prop key="p2">xxx</prop>
</props>
</property>
</bean>
<bean id="user1" class="com.itheima.domain.User">
<property name="name" value="tom"/>
<property name="addr" value="beijing"/>
</bean>
<bean id="user2" class="com.itheima.domain.User">
<property name="name" value="lokey"/>
<property name="addr" value="tianjin"/>
</bean>
Map集合配置时需要创建一个单独的domain.User来注入其中引入的user1和user2
User文件
package com.itheima.domain;
public class User {
private String name;
private String addr;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", addr='" + addr + '\'' +
'}';
}
public void setAddr(String addr) {
this.addr = addr;
}
}
Properties集合配置
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype">
<property name="properties">
<props>
<prop key="p1">ppp</prop>
<prop key="p2">xxx</prop>
</props>
</property>
</bean>
测试结果
分模块开发
实际开发中Spring的配置内容非常多,这就导致了Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中(在resources中创建多个配置文件),进行分类,最后通过import标签配置于主配置文件进行加载。列如:按照面向的对象或者面向的层去拆。或者对应的配置文件对应其实现的类对应的方法。使得开发过程中逻辑清晰,代码整洁、思路顺畅。
主配置文件使用import标签引入其他分配置文件
在beans标签内引入即可
<import resource="applicationContext-product.xml"/>
<!-- <import resource="applicationContext-user.xml"/>
Spring相关API
1.ApplicationContext的实现类
(1)ClassPathXmlApplicationContext
它是从类的根路径下加载配置文件时推荐使用
(2)FileSystemXmlApplictionContext
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
(3)AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建spring容器。它用来读取注解。
2.getBean();的两种方法
第一种方式是根据Bean的id来获取容器中的对象(使用于容器中的Bean存在多个时)
第二种方式是获取某一类型的Bean(适用于容器中的Bean只有一个时)
Spring配置数据源(导入第三方jar包)
1.数据源(连接池)的作用
· 数据源(连接池)是为了提高程序性能而出现的
· 实现实列化数据源,初始化部分连接资源
· 使用连接资源时从数据源中获取
· 使用完毕后将连接资源归还给数据源(也就是常说的关闭资源)
2.创建数据源(导入第三方jar包)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
3.测试手动创建的数据源是否导入成功
测试手动创建c3p0数据源
@Test
// 测试手动创建c3p0数据源
public void test1() throws Exception {
// 获取数据库连接对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 注册驱动
dataSource.setDriverClass("com.mysql.jdbc.Driver");
// 设置地址连接数据库
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/java_web02");
dataSource.setUser("root");
dataSource.setPassword("root");
// 获取资源
Connection connection = dataSource.getConnection();
System.out.println(connection);
//关闭资源
connection.close();
}
}
测试结果
测试手动创建druid数据源对象
@Test
// 测试手动创建druid数据源
public void test2() throws Exception {
// 获取数据库连接对象
DruidDataSource dataSource = new DruidDataSource();
// 注册驱动
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
// 设置地址连接数据库
dataSource.setUrl("jdbc:mysql://localhost:3306/java_web02");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 获取资源
DruidPooledConnection connection = dataSource.getConnection();
System.out.println(connection);
//关闭资源
connection.close();
}
测试结果
4.抽取配置文件:抽取jdbc
抽取配置文件是为了让具体的字符串与我们的数据源解耦合、在开发过程中我们尽量追求高内聚低耦合
内聚
内聚是一个模块内部各成分之间相关联程度的度量。
耦合性
耦合性也叫块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块 之间联系越紧密,其耦合性就越强,模块之间越独立则越差,模块间耦合的高低取决于模块 间接口的复杂性,调用的方式以及传递的信息。
配置文件如下
测试代码
@Test
// 测试手动创建c3p0数据源(加载properties配置文件)
public void test3() throws Exception {
// 读取配置文件
ResourceBundle rb = ResourceBundle.getBundle("jdbc");
String driver = rb.getString("jdbc.driver");
String url = rb.getString("jdbc.url");
String username = rb.getString("jdbc.username");
String password = rb.getString("jdbc.password");
// 设置数据源对象、设置连接参数
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
// 获取资源
Connection connection = dataSource.getConnection();
System.out.println(connection);
//关闭资源
connection.close();
}
其中ResourceBundle方法时专门用来获取配置文件的
测试结果
Spring配置数据源对象
1.将DataSource的创建权交给Spring容器去完成
步骤与Spring的开发步骤一致
(1) 配置datasource
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/java_web02"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
测试文件
@Test
public void test4() throws Exception{
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = app.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
测试结果
注:在开发过程中我们有个习惯:让每个配置文件专注于自己的范围(列如我要修改数据库配置时找到properties进行修改要修改Bean的配置时找到我们的xml文件进行修改,让操作步骤简洁明了)
2.将properties配置文件配置信息抽取进applicationContext.xml文件中
context:property-placeholder // 属性加载器
用value = ${key} key代表properties配置文件内的配置名
Spring原始注解
1.Spring原始注解(主要是替代的配置),Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解替代xml配置文件可以简化配置,提高开发效率。
Dao层的替代、srrvice层的替代以及依赖的注入替代
可以使用@Component(“userDao”)或者@Repository(“userDao”)Spring容器中userDao方法的注入
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
//<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
// @Component("userDao")和@Repository("userDao")的作用是一样的,知识@Repository("userDao")针对的是Dao层、@Service针对的是service层
//@Component("userDao")
@Repository("userDao")
public class UserDaoImpl implements UserDao {
public void save(){
System.out.println("save running.........");
}
}
可以使用@Component(“userService”)或者@Service(“userService”)替代Spring容器中userService方法的注入
// <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
//@Component("userService")
@Service("userService")
public class UserServiceImpl implements UserService {
其中@Autowired方法在你不写下面的方法时可以依照数据类型从Spring容器中进行匹配。适用于同一个bean只有一个时。
@Qualifier()方法是按照id值从容器中进行匹配的,但是要配合@Autowired一起使用
@Resource(name = “”)相当于@Autowired+@Qualifier()
为了让组件扫描更清晰的知道所扫描的bean是属于那一层的可以用下面几种方法来配置对应层的bean
其中@Autowired方法在你不写下面的方法时可以依照数据类型从Spring容器中进行匹配。适用于同一个bean只有一个时。
@Qualifier()方法是按照id值从容器中进行匹配的,但是要配合@Autowired一起使用
@Resource(name = “”)相当于@Autowired+@Qualifier()
// @Autowired
// @Qualifier("userDao")
@Resource(name = "userDao")
private UserDao userDao;
可以使用@Scope("singleton") @Scope("protopyte")
写在类名的上面来标注这个bean是单个的还是多个的(Bean的作用范围),只可以同时使用一个
@Service("userService")
//@Scope("singleton")
//@Scope("protopyte")
public class UserServiceImpl implements UserService {
可以使用@Value(“${jdbc.driver}”)方法来进行普通数据类型的注入以及jdbc.properties里内容的注入
@Value("${jdbc.driver}")
private String driver;
可以使用@PostConstruct和@PreDestroy来来替代Spring容器中初始化方法和销毁方法的注入
@PostConstruct
public void init(){
System.out.println("Service对象的初始化方法");
}
@PreDestroy
public void destory(){
System.out.println("Service对象的销毁方法");
}
2.配置组件扫描
使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是对我们在某个包及其子包下的Bean进行扫描并帮我们去解析完成注入
<!--配置组件扫描-->
<context:component-scan base-package="com.itheima"/>
以上这些就是spring的原始注解下面来学习Spring的新注解
因为原始注解对于我们自定义的bean束手无策所以要使用新注解
Spring新注解
· 使用Spring的原始注解还不能替代xml配置文件,还需要使用注解替代的配置如下
*非自定义的Bean的配置:<bean>
*加载peoperties文件的配置:<context:property-placeholder>
*组件扫描的配置:<context:component-scan>   *引入其他配置文件
:<import>
对应的替代如下
1.首先写入标志类,代表该类是Spring的核心配置类、意味着用类的方式替代文件,用注解的方式去替代标签
// 标志该类是Spring的核心配置类意味着用类的方式代替文件,用注解的方式去替代标签
@Configuration // 写在类名上面
2.相应的类注解替代标签
1、(配置组件扫描)
//<!--配置组件扫描-->
//<context:component-scan base-package="com.itheima"/>
@ComponentScan("com.itheima") // 写在标签类下
2、(加载外部的properties文件)
//<!-- 加载外部的properties文件-->
//<!-- <context:property-placeholder location="classpath:jdbc.properties"/>-->
@PropertySource("jdbc.driver") // 同样写在类名上面即可
3、(方法的依赖注入)
@Bean("dataSource") //Spring会将当前方法的返回值以指定名称存储到Spring容器中
public void save(){
System.out.println("go go go");
}
在新注解配置中同样可以使用分模块开发,用以下方法将分配置抽取到总配置中,实现时就只需要去实现总方法即可调用所有的分方法(@Import()括号内时数组类型的,可以通过,分隔注入多个分配置名)
// <import resource=""/>
@import({DataSourceConfiguration.class,XX.class})
另外可以用@Value(${El表达式}) private String driver;
的方式来替代properties中的配置文件
控制层读取时使用以下方式读取
使用新注解时,我们的测试方法也得相应的改变 (读取的时总配置文件)
Spring集成Junit
· 让SpringJunit负责创建Sping容器,但是需要将配置文件的名称告诉他
· 将需要进行测试的Bean直接在测试类中进行注入
Spring集成Junit步骤
1.导入spring集成Junit的坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
2.使用@Runwith
注解替换原来的运行期
// 替换原来的运行期
@RunWith(SpringJUnit4ClassRunner.class)
3.使用@ContextConfiguration
指定配置文件或配置类
//内部指定配置文件的位置
@ContextConfiguration("classpath:applicationContext.xml")
4.使用@Autowired
注入需要测试的对象(可以注入多个对象,写在需要注入的对象上面即可)
public class SpringJunitTest {
// 注入需要测试的对象
@Autowired
private UserService userService;
完成测试代码如下
package com.itheima.test;
import com.itheima.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
// 替换原来的运行期
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml") //内部指定配置文件的位置
public class SpringJunitTest {
// 注入需要测试的对象
@Autowired
private UserService userService;
@Test
public void test1(){
userService.save();
}
}
service类的实现类实现的代码如下
@Resource(name = "userDao")
private UserDao userDao;
public void save() {
System.out.println();
userDao.save();
}
@PostConstruct
public void init(){
System.out.println("Service对象的初始化方法");
}
@PreDestroy
public void destory(){
System.out.println("Service对象的销毁方法");
}
}
测试结果如下
Spring的AOP(面向切面编程)
AOP简介
+ 作用:在程序运行期间,在不修改源码的情况对方法进行功能增强
+ 优势: 减少重复代码,提高开发效率,并且便于维护
+ AOP的底层实现: 实际上,AOP的底层是通过Spring提供的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强。
AOP的动态代理技术
+ JDK代理:基于接口的动态代理技术
+ cglib代理:基于父类的代理技术
在这个图片中左边是目标对象连接目标接口,目标接口就有了目标对象的方法,代理对象又连接目标接口,既代理对象也有了目标接口的所有方法。
右边图像(第三方的)是没有接口时代理对象进行一系列操作生成目标对象的子类,调用代理对象时父子类的方法相同,构成连接。 注:不是继承
AOP的相关术语
目标对象:能且有可能被增强的方法
代理:增强的方法
连接点:可以被增强的方法
切入点:真正配置可以被增强的连接点
通知/增强:在截到目标方法后增强的途中必然有一段增强的逻辑,这个逻辑要放入一个方法当中。这个方法被称为通知/增强
切面:目标+增强=切面 或者 切点+通知=切面
织入:切点跟增强结合的过程称为一个织入过程
AOP底层开发明确的事项
1. 需要编写的内容
编写核心业务代码(目标类的目标方法)
编写切面类,切面类中有通知(增强功能方法)
在配置文件中,配置织入关系,即将那些通知与那些连接点进行结合
2. AOP技术实现的内容
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行
3. AOP底层使用那种代理方式
在Spring中,框架会根据目标类是否实现了接口来决定采用那种动态代理的方式。
AOP开发步骤
基于XML的AOP开发
1. 导入AOP相关坐标
// spring容器的测试坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.14</version>
</dependency>
2.创建目标接口和目标类(内部有切点)
目标接口
package com.itheima.aop;
public interface TargetUInterface {
public void save();
}
目标类
package com.itheima.aop;
public class Target implements TargetUInterface {
public void save(){
System.out.println("save running.........");
}
}
3.创建切面类(内部有增强方法)
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
public void before(){
System.out.println("前置增强");
}
public void afterReturning(){
System.out.println("后置增强");
}
// ProceedingJoinPoint: 正在执行的连接点 == 切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强..");
Object proceed = pjp.proceed();
System.out.println("环绕后增强..");
return proceed;
}
public void afterThrowing(){
System.out.println("异常抛出增强......");
}
public void after(){
System.out.println("最终增强......");
}
}
4.将目标类和切面类的对象创建权交给spring
<!--目标对象-->
<bean id="target" class="com.itheima.aop.Target"></bean>
<!--切面对象-->
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>
5.在applicationContext.xml中配置织入关系
<!--配置织入,告诉spring框架 那些方法(切点)需要进行那些增强(前置、后置....)-->
<aop:config>
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.save())"/>
</aop:aspect>
</aop:config>
6.测试代码
package com.itheima.test;
import com.itheima.aop.TargetUInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
// 替换原来的运行期
@RunWith(SpringJUnit4ClassRunner.class)
// 指定配置文件或配置类
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetUInterface target;
@Test
public void test1(){
target.save();
}
}
运行结果
知识要点
XML配置AOP详解
1. 切点表达式的写法
访问修饰符可以省略
返回子类型、包名、类名、方法名可以使用星号* 代表任意的
包名与类名之间一个点,代表当前包下的类,两个点… 表示当前包及其子包下的类
参数列表可以使用两个点…表示任意个数,任意类型的参数列表
列如
2. 通知的类型
3. 展示各种通知
切面类如下
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
public void before(){
System.out.println("前置增强");
}
public void afterReturning(){
System.out.println("后置增强");
}
// ProceedingJoinPoint: 正在执行的连接点 == 切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强..");
Object proceed = pjp.proceed();
System.out.println("环绕后增强..");
return proceed;
}
public void afterThrowing(){
System.out.println("异常抛出增强......");
}
public void after(){
System.out.println("最终增强......");
}
}
完整xml配置如下
<!--目标对象-->
<bean id="target" class="com.itheima.aop.Target"></bean>
<!--切面对象-->
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>
<!--配置织入,告诉spring框架 那些方法(切点)需要进行那些增强(前置、后置....)-->
<aop:config>
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut="execution(public void com.itheima.aop.*.*(..))"/>
<aop:after-returning method="afterReturning" pointcut="execution(public void com.itheima.aop.*.*(..))"/>
<aop:around method="around" pointcut="execution(public void com.itheima.aop.*.*(..))"/>
<aop:after-throwing method="afterThrowing" pointcut="execution(public void com.itheima.aop.*.*(..))"/>
<aop:after method="after" pointcut="execution(public void com.itheima.aop.*.*(..))"/>
</aop:aspect>
</aop:config>
测试结果
手动在目标接口插入异常后测试
package com.itheima.aop;
public class Target implements TargetUInterface {
public void save(){
System.out.println("save running.........");
int i = 1/0;
}
}
测试结果
切点表达式的抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式。目的是便于维护。
现在我们针对刚才的配置文件进行简单修改
<!--配置织入,告诉spring框架 那些方法(切点)需要进行那些增强(前置、后置....)-->
<aop:config>
<aop:aspect ref="myAspect">
<!-- 抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.Target.save())"></aop:pointcut>
<!-- 调用抽取的表达式-->
<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:around method="around" pointcut-ref="myPointcut"/>
<aop:after method="after" pointcut-ref="myPointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
测试结果
知识要点
基于注解的AOP开发
开发步骤
1. 创建目标接口和目标类
package com.itheima.anno;
public interface TargetUInterface {
public void save();
}
package com.itheima.anno;
import org.springframework.stereotype.Component;
@Component("Target")
public class Target implements TargetUInterface {
public void save(){
System.out.println("save running.........");
}
}
2. 创建切面类
package com.itheima.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("MyAspect")
@Aspect // 标注当前MyAspect是一个切面类
public class MyAspect {
@Before("execution(* com.itheima.anno.*.*(..))")
public void before(){
// 配置前置通知
System.out.println("前置增强");
}
public void afterReturning(){
System.out.println("后置增强");
}
// ProceedingJoinPoint: 正在执行的连接点 == 切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强..");
Object proceed = pjp.proceed();
System.out.println("环绕后增强..");
return proceed;
}
public void afterThrowing(){
System.out.println("异常抛出增强......");
}
public void after(){
System.out.println("最终增强......");
}
}
3. 将目标类和切面类的对象创建权交给spring
@Component("MyAspect")
@Aspect // 标注当前MyAspect是一个切面类
public class MyAspect {
4. 在切面类中使用注解配置织入关系
public class MyAspect {
@Before("execution(* com.itheima.anno.*.*(..))")
public void before(){
// 配置前置通知
System.out.println("前置增强");
}
5. 在配置文件中开启组件和AOP的自动代理
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
<!--开启组件扫描-->
<context:component-scan base-package="com.itheima.anno"/>
<!-- aop的自动代理-->
<aop:aspectj-autoproxy/>
6. 测试
测试代码
package com.itheima.test;
import com.itheima.anno.TargetUInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
// 替换原来的运行期
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class AnnoTest {
@Autowired
private TargetUInterface target;
@Test
public void test1(){
target.save();
}
}
测试结果
知识要点
大学是人生中唯一可以无忧无虑只为自己努力的阶段
SpringJdbcTemplate基本使用
SpringJdbcTemplate简介
其实SpringJdbcTemplate就是spring为我们提供的jdbc的操作工具
SpringJdbcTemplate概述
Spring框架有简化操作的优点,所以它是spring框架中提供的一个对象,是对原始繁琐的Jbdc API对象的简单封装。Spring框架为我们提供了很多的操作模板类,在封装各种数据类时我们使用spring提供的操作模板类即可。
JdbcTemplate开发步骤
1. 导入spring-jdbc和spring-tx坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
2. 创建数据库表和实体
数据库
String sql = "create table count (id int primary key auto_increment, " +
"uname varchar(100) not null unique, money double not null)";
实体
package com.itheima.domain;
public class Account {
private String uname;
private double money;
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"uname='" + uname + '\'' +
", money=" + money +
'}';
}
}
3. 创建JdbcTemplate对象
// 创建JdbcTemplate对象
JdbcTemplate jdbcTemplate = app.getBean(JdbcTemplate.class);
4. 执行数据库操作
package com.itheima.test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
public class JdbcTemplateTest {
// 测试JdbcTemplate开发步骤
public static void main(String[] args) throws PropertyVetoException {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// 创建数据源对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/java_web02");
dataSource.setUser("root");
dataSource.setPassword("root");
JdbcTemplate jdbcTemplate = new JdbcTemplate(); //创建模板对象
// 设置数据源对象,知道数据库对象在哪
jdbcTemplate.setDataSource(dataSource);
// 执行操作
int row = jdbcTemplate.update("insert into count values(?,?,?)", 1,"tom", 5000);
System.out.println(row);
}
}
测试结果
Spring产生JdbcTemplate对象
我们可以将JdbcTemolate的创建权交给Spring,将数据源DataSource的创建权也交给Spring,在Spring容器内部将数据源DataSource注入到JdbcTemplate模板对象中,配置如下:
这里我们直接使用抽取jdbc.properties工具类的方法来实现
XML配置如下
<!-- 加载jdbc.properties外部文件-->
<contetx:property-placeholder location="classpath:jdbc.properties"/>
<!-- 创建数据源对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--创建JdbcTemplate模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
jdbc.properties配置如下
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/java_web02
jdbc.username=root
jdbc.password=root
测试代码
// 测试JdbcTemplate开发步骤
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
JdbcTemplate jdbcTemplate = app.getBean(JdbcTemplate.class);
int row = jdbcTemplate.update("insert into count values(?,?,?)", 4,"xiaozhao", 5000);
System.out.println(row);
}
}
测试结果
Spring集成Junit实现增删改查
测试类
package com.itheima.test;
import com.itheima.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
// 改变运行周期
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateCRUDTest {
// 注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
// 修改
public void test1update(){
jdbcTemplate.update("update count set money=? where uname=?",10000,"tom");
}
@Test
// 删除
public void test2delete(){
jdbcTemplate.update("delete from count where uname=?","xiaozhao");
}
@Test
//查询
public void test3queryAll(){
List<Account> accountList = jdbcTemplate.query("select * from count", new BeanPropertyRowMapper<Account>(Account.class));
System.out.println(accountList);
}
@Test
public void test3queryOne(){
Account account = jdbcTemplate.queryForObject("select * from count where uname=?", new BeanPropertyRowMapper<Account>(Account.class), "lisi");
System.out.println(account);
}
@Test
public void test4querycount(){
Long count = jdbcTemplate.queryForObject("select count(*) from count", Long.class);
System.out.println(count);
}
}
运行结果
知识要点
1.JdbcTemplate对象在创建完之后要为其创建数据源对象
2.修改操作统一用update主要在sql语句区分,方法查询操作中查询多个对象用query结果是一个list集合,Mapper一般是new BeanPropertyRowMapper,params代表占位符。如果查询一个值用queryForObject,在查询结果是一个简单类型是params可以换成一个requerytab(列Long.class)。
Spring的事务声明式事务控制
编程式就是你自己用java的API去写代码控制事务列如jdbc中的commit,声明式就是通过配置去实现
声明式好处,能解耦合,在不写代码的情况下可以对业务层的很多方法进行事务控制,简化开发
编程式事务控制相关对象
基于XML的声明式事务控制
Spring的声明式事务控制顾名思义就是采用声明的方式来处理事务,这里所说的声明,就是指在配置问价中声明,用在Spring配置文件中声明式的处理事务来替代代码式的处理事务
声明式事务处理的作用:你的业务代码是你的业务代码,你的事务控制是你的事务控制,我用配置的方法让你的业务和事务完成一个松耦合,在业务代码运行时就可以完成事务控制,类似于切点与增强。
声明式事务控制明确事项
切点 业务方法
通知 事务控制
切面 切点和通知进行AOP配置
事务的增强用advisor
Isolation 隔离级别 propagation 传播行为 timeout 失效时间 read-only 是否只读
name 后面是需要配置的方法
还可以使用通配符方法名*的方法,代表以这个方法名开头的所有方法
name后面不写代表默认 隔离级别等。
知识要点
在使用注解方式配置spring的事务控制时要配置事务的注解驱动
小结
知识要点
Spring集成Web环境
1. 创建UserDao接口和UserService接口以及对应的实现类
接口
package com.itheima.dao;
public interface UserDao {
public void save();
}
package com.itheima.service;
public interface UserService {
public void save();
}
实现类
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
public class UserDaoImpl implements UserDao {
public void save(){
System.out.println("save running........");
}
}
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.save();
}
}
2. 创建servlet的web层
package com.itheima.web;
import com.itheima.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = app.getBean(UserService.class);
userService.save();
}
}
3. 配置xml文件和web.xml文件
<!-- 配置Dao-->
<bean id="useDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<!-- 配置userService-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="useDao"/>
<servlet>
<servlet-name>UserServlet</servlet-name>
<servlet-class>com.itheima.web.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserServlet</servlet-name>
<url-pattern>/use.do</url-pattern>
</servlet-mapping>
4. 启动tomcat并且访问我们设置的路径
输出结果
ApplicationContext应用上下文获取方式
创建一个监听器的类连接接口 如下
package com.itheima.listener;
import jdk.nashorn.internal.ir.RuntimeNode;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ContextLoaderListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// 将Spring的应用上下文对象存储到ServletContext域中
ServletContext servletContext = servletContextEvent.getServletContext();
servletContext.setAttribute("app", app);
System.out.println("Spring容器创建完毕");
}
public void contextDestroyed(ServletContextEvent sce) {
}
使用监听器初始化里的ServletContextEvent.getServletContext()方法获取到ServletContext域.然后用ServletContext提供的setAttribute()方法将我的接口方法封装到监听器方法中,在web层的方法需要调用我的上下文对象时就只需要调用我在监听器中封装的方法即可,有效的防止了上面的弊端。
package com.itheima.web;
import com.itheima.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
ServletContext servletContext = this.getServletContext();
ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app");
UserService userService = app.getBean(UserService.class);
userService.save();
}
}
获取servletContext( ) 的两种方式
ServletContext servletContext = this.getServletContext();
ServletContext servletContext = req.getServletContext();
在web.xml中配置ContextLoaderListener监听器
<!-- 配置监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
访问配置的地址后测试结果