Spring5基础
Spring5
简介
开发步骤
1.导入基本依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency> </dependencies>
2.编写Dao接口和实现类
public interface UserDao { public void save(); }
public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("save running..."); } }
3.创建spring核心配置文件(约定熟成为applicationContext.xml)
4.在spring配置文件中配置实现类
<bean id="userDao" class="com.ember.dao.impl.UserDaoImpl"></bean>
5.创建ApplicationContext对象获取getBean,随后就可以调用相应实体类的方法
public class UserDaoDemo { public static void main(String[] args) { ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao =(UserDao) app.getBean("userDao"); userDao.save(); } }
Spring配置文件
Bean标签基本配置
Bean标签范围配置
默认的是singleton,此时我们getBen相同的id获得的对象地址是一样的
反之更改scope再执行相同的代码我们发现不一样了
再来看看UserDaoImpl的创建时机也是有所不同的
当我们scope是singleton时我们断点调试发现再读取配置文件时就创建了
且整个过程只创建一次
而当我们改变scope后,对象会在getBean时才创建
Bean生命周期配置
init-method:指定类中的初始化方法名称
destroy-method:指定类中销毁方法名称
在实现类中完成指定的方法后,在配置文件中配置后,当bean对象创建和销毁时就会执行指定的方法
public class AppForinstanceUser { public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); //设置关闭钩子,在结束时关闭销毁容器,会执行其销毁方法(在任何位置都可以) contex.registerShutdownHook(); User user = (User) context.getBean("user"); user.save(); } }
public void init(){ System.out.println("初始化方法。。。。"); } public void destroy(){ System.out.println("销毁方法。。。。"); } //要执行销毁可以手动关闭,后期我们通过设置关闭钩子关闭
<bean id="userDao" class="com.ember.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
还可以通过使用接口的方式控制器创建,销毁方法(可以了解下)
Bean实例化的三种方式
spring默认的是使用无参构造实例化(也是掌握的重点)
1) 使用无参构造方法实例化
它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败
class直接传入的是实现类的路径
2) 工厂静态方法实例化
工厂的静态方法返回Bean实例
学过静态方法的都知道不用通过类实例,直接通过类名就可以调用方法,所以这里的class是工厂类名
package com.ember.factory; import com.ember.dao.UserDao; import com.ember.dao.impl.UserDaoImpl; public class StaticFactory { public static UserDao getUserDao(){ return new UserDaoImpl(); } }
<bean id="userDao" class="com.ember.factory.StaticFactory" factory-method="getUserDao"></bean>
3) 工厂实例方法实例化
工厂的非静态方法返回Bean实例
这里和方法的调用原理差不多(没有使用静态方法所以不能使用类名调用方法,要先创建类的实例化再调用方法)要先获取工厂对象,在通过方法获取getUserDao
public class StaticFactory { public UserDao getUserDao(){ return new UserDaoImpl(); } }
<bean id="factory" class="com.ember.factory.StaticFactory"></bean> <bean id="userDao" class="com.ember.factory.StaticFactory" factory-bean="factory" factory-method="getUserDao"></bean>
现在有个改良的工厂实例化bean的方式,很重要,通过实现自带的FactoryBean接口(使用了泛型)
public class UserDaoFactoryBean implements FactoryBean<User> { //代替原始实例方法中创建对象的方法 @Override public User getObject() throws Exception { return new UserDaoImpl(); } //对象的类型 @Override public Class<?> getObjectType() { return User.class; } }
这个工厂类使用了泛型,你要创建什么类型的对象就传入什么类型就好了,这种方式在后期整合其他框架的时候很常用,这样创建的bean默认是单例的,如果要改就重写这个接口的还有一种方方法isSingleton --return true就是单例的
<!--方式四使用factorybean实例化--> <bean id="user" class="com.ember.factory.UserDaoFactoryBean"></bean>
public class AppForinstanceUser { public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); user.save(); } }
后期的service层也是可以写到配置文件中通过getbean获取调用方法的
依赖注入
我们学习了javaweb后,我们发现后端一般使用三层架构方式,分别为Dao层,service和web层,dao层用于方法的定义,service用于调用相应的方法,web层则通过service对象调用方法。
如果我们将service和dao对象的创建就会存在一个问题:
UserService实例和UserDao实例都存在与Spring容器中,当前的做法是在容器外部获得UserService实例和UserDao实例,然后在程序中进行结合。那么我们就要在测试类中使用两次getBean
getBean(name:"userService")
getBean(name:"userDao")
那我们为啥不在xml对bean做一个嵌套bean呢
Bean的依赖注入概念
依赖注入(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。
IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。
简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
依赖注入方式
构造方法(有参)
1.创建有参构造(同样也要创建无参构造)
public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl(){ } public UserServiceImpl(UserDao userDao){ this.userDao=userDao; }
配置Spring容器调用有参构造时进行注入
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"> <constructor-arg name="userDao" ref="userDao"></constructor-arg> <!--这个标签的name对应的是构造方法的参数名,ref同样是上面bean的id--!> </bean>
set方法
//在UserServiceImpl中添加setUserDao方法 public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void save() { userDao.save(); } }
<!-- 配置Spring容器调用set方法进行注入 --> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/></bean> <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> <!--ref引用上面的bean id实例化userDao传入set方法(参数名也为userDao)--!> <!--注意property 标签里面的name对应的是对应的set方法set后面的单词(第一个字母变小写)--!> </bean>
这种方式比较麻烦,spring给出了简化方式——P命名空间注入
P命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件中,如下:
首先,需要引入P命名空间:
xmlns:p="http://www.springframework.org/schema/p"
其次,需要修改注入方式
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" p:userDaoref="userDao"/>
Bean的依赖注入的数据类型
注入数据的三种数据类型
对象类型采用ref引入bean的id,普通的数据类型就用value就ok了
普通数据类型
public class UserDaoImpl implements UserDao { private String company; private int age; public void setCompany(String company) { this.company = company; } public void setAge(int age) { this.age = age; } public void save() { System.out.println(company+"==="+age); System.out.println("UserDao save method running...."); } }
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="company" value="传智播客"></property> <property name="age" value="15"></property> </bean>
引用数据类型
就是注入的是对象数据类型,我们通过ref传入对应bean的id
<bean id="userService" class="com.ember.service.impl.UserServiceImpl"> <property name="user" ref="user"></property> </bean>
集合数据类型
集合数据类型(List
public class UserDaoImpl implements UserDao { private List<String> strList; public void setStrList(List<String> strList) { this.strList = strList; } public void save() { System.out.println(strList); System.out.println("UserDao save method running...."); } }
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="strList"> <list><value>aaa</value> <value>bbb</value> <value>ccc</value> </list> </property> </bean>
集合数据类型(List
public class UserDaoImpl implements UserDao { private List<User> userList; public void setUserList(List<User> userList) { this.userList = userList; } public void save() { System.out.println(userList); System.out.println("UserDao save method running...."); } }
<bean id="u1" class="com.itheima.domain.User"/> <bean id="u2" class="com.itheima.domain.User"/> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="userList"> <list><bean class="com.itheima.domain.User"/> <bean class="com.itheima.domain.User"/> <ref bean="u1"/> <ref bean="u2"/> </list> </property> </bean>
集合数据类型( Map<String,User> )的注入
public class UserDaoImpl implements UserDao { private Map<String,User> userMap; public void setUserMap(Map<String, User> userMap) { this.userMap = userMap; } public void save() { System.out.println(userMap); System.out.println("UserDao save method running...."); } }
<bean id="u1" class="com.itheima.domain.User"/> <bean id="u2" class="com.itheima.domain.User"/> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="userMap"> <map><entry key="user1" value-ref="u1"/> <entry key="user2" value-ref="u2"/> </map> </property> </bean>
集合数据类型(Properties)的注入
public class UserDaoImpl implements UserDao { private Properties properties; public void setProperties(Properties properties) { this.properties = properties; } public void save() { System.out.println(properties); System.out.println("UserDao save method running...."); } }
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="properties"> <props> <prop key="p1">aaa</prop> <prop key="p2">bbb</prop> <prop key="p3">ccc</prop> </props> </property> </bean>
引入其他配置文件(分模块开发)
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他
配置文件中,而在Spring主配置文件通过import标签进行加载
<import resource="applicationContext-xxx.xml"/>
自动装配
spring提供了一个非常强大的功能,在bean里面通过autowire属性实现自动装配(自动注入),就是不用写注入了(这种只能取代setter注入的方式,必须提供set方法)
<bean id="userService" class="com.ember.service.impl.UserServiceImpl" autowire="byType"></bean>
自动装配要注意几点
小结
id属性:在容器中Bean实例的唯一标识,不允许重复
class属性:要实例化的Bean的全限定名
scope属性:Bean的作用范围,常用是Singleton(默认)和prototype
name属性:属性名称
value属性:注入的普通属性值
ref属性:注入的对象引用值
标签
Spring相关API
ApplicationContext的继承体系
applicationContext:接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象
*ApplicationContext的实现类
ClassPathXmlApplicationContext**
它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext**
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
getBean()方法使用
public Object getBean(String name) throws BeansException { assertBeanFactoryActive(); return getBeanFactory().getBean(name); } public <T> T getBean(Class<T> requiredType) throws BeansException { assertBeanFactoryActive(); return getBeanFactory().getBean(requiredType); }
其中,当参数的数据类型是字符串时,表示根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。当参数的数据类型是Class类型时,表示根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。
之前我们使用的就是传入bean的id的方法,这种方法适用于有多个同类型的bean
下面这种使用反射获取,就只适合没有多个同类的bean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService1 = (UserService) applicationContext.getBean("userService"); UserService userService2 = applicationContext.getBean(UserService.class);
Spring配置数据源
数据源作用
• 数据源(连接池)是提高程序性能出现的
• 事先实例化数据源,初始化部分连接资源
• 使用连接资源时从数据源中获取
• 使用完毕后将连接资源归还给数据源
常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等
数据源开发步骤
这里其实就是为了和数据库建立连接,和我们之前学的JDBC功能能是一样的,只是现在使用了连接池,我们要通过连接池封装的对象获取连接
① 导入数据源的坐标和数据库驱动坐标
<!-- 测试类--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- 数据库--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!-- c3p0连接池--> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency>
② 创建数据源对象
③ 设置数据源的基本连接数据
④ 使用数据源获取连接资源和归还连接资源
不同的数据源上面三中的代码有一点点变化,但是学了jdbc是很好理解的,下面给出c3p0和druid的实现代码加以区分
@Test //测试druid数据源 public void test2() throws SQLException { //创建数据源对象 DruidDataSource dataSource=new DruidDataSource(); //.设置基本连接信息driver url user password dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/sql_store?characterEncoding=utf-8&serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("20001103"); //.获取连接 DruidPooledConnection connection = dataSource.getConnection(); System.out.println(connection); //.释放资源 connection.close(); } @Test //测试手动创建c3p0数据源 public void test1() throws PropertyVetoException, SQLException { //创建数据源对象 ComboPooledDataSource dataSource=new ComboPooledDataSource(); //.设置基本连接信息driver url user password dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/sql_store?characterEncoding=utf-8&serverTimezone=UTC"); dataSource.setUser("root"); dataSource.setPassword("20001103"); //.获取连接 Connection connection = dataSource.getConnection(); System.out.println(connection); //.释放资源 connection.close(); }
同理这种链接的代码可能有很多如果直接在类里面写死后期维护修改很不方便,我们仍然还是用配置文件比较好
我们来回顾一下
1.resources下创建好存放连接信息的配置文件xxxx.properties文件
2.使用就好了
@Test //测试druid数据源(加载配置文件) public void test3() throws SQLException { //.读取配置文件 ResourceBundle rb=ResourceBundle.getBundle("jdbc"); String driver=rb.getString("jdbc.driver"); String url=rb.getString("jdbc."); String username=rb.getString("jdbc.username"); String password=rb.getString("jdbc.password"); //创建数据源对象 DruidDataSource dataSource=new DruidDataSource(); //.设置基本连接信息driver url user password dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); //.获取连接 DruidPooledConnection connection = dataSource.getConnection(); System.out.println(connection); //.释放资源 connection.close(); }
上面呢是我们之前学习的知识回顾相当于
Spring配置数据源
回顾:之前我们学的javaweb是用mybatis配置数据源,我们直接将数据库连接信息存到了mybatis的核心配置文件里,再通过sqlsessionfactory来获取 sqlsession,再通过mapper代理建立连接
我们观察手动配置的代码,发现都是通过set方法去设置连接信息
而spring注入的一个主要方式就是通过set方法注入,这不是刚刚好吗
可以将DataSource的创建权交由Spring容器去完成
DataSource有无参构造方法,而Spring默认就是通过无参构造方法实例化对象的 DataSource要想使用需要通过set方法设置数据库连接信息,而Spring可以通过set方法进行字符串注入
首先引入spring的坐标(之前已经弄好了)
再写配置文件
<bean id="dataSources" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/sql_store?characterEncoding=utf-8&serverTimezone=UTC"></property> <property name="user" value="root"></property> <property name="password" value="20001103"></property> </bean>
配置数据源
@Test //测试Spring容器配置数据源 public void test4() throws SQLException { ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSources = app.getBean(DataSource.class); Connection connection = dataSources.getConnection(); System.out.println(connection); connection.close(); }
其实这样已经是解耦合了,我们以后要改sql连接信息直接到xml中修改就好了,但是我们任然可以将配置文件导入spring的配置文件,以后直接去改配置文件
applicationContext.xml加载jdbc.properties配置文件获得连接信息。
首先,需要引入context命名空间和约束路径:
命名空间:xmlns:context="http://www.springframework.org/schema/context"
约束路径:http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
这个直接spring配置文件中复制第一个连接和schemaLocation连接,将里面的beans全改成context就可以了
然后在下面用context:property-placeholder/>引入配置文件文件
配置如下
<?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"> <!-- <bean id="userDao" class="com.ember.dao.impl.UserDaoImpl"></bean>--> <!-- 加载外部的properties文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 当我们后期工程比较大时,配置文件比较多还要导入类路径中或jar包中的properties文件我们就要写location="classpath*:*.properties"-->
最后通过EL表达式引入配置文件中的值就好了
这个和mybatis一个样
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean>
Spring注解开发
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。
<!--注解的组件扫描--> <context:component-scan base-package="com.itheima"></context:component-scan>
注意:
只使用原始注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。
如果使用全注解开发(原始注解和新注解都使用),那么可以完全替代spring核心配置文件了
Spring原始注解
Spring原始注解主要是替代
我们主要使用注解来替代我们之前写好的xml来进行对照学习
bean的代替用@Component("id名")
/*<bean id="userService" class="com.ember.service.impl.UserServiceImpl"> </bean>*/ @Component("userService") public class UserServiceImpl implements UserService {
bean标签的scop属性通过@Scope注解代替
@Scope("prototype")//多 @Scope("singleton")//单
对象注入的代替(再要注入的对象前加上@Autowired @Qualifier("bean对应的id")),使用注解后set方法就可以不写了
可以不写@Qualifier指定bean对象,@Autowired默认根据数据类型从spring容器中进行匹配
// <property name="userDao" ref="userDao"></property> @Autowired @Qualifier("userDao") private UserDao userDao;
这里注意如果@Resource报错就到pom里面加一个依赖 javax.annotation-api
@Resource(name="userDao")//相当于@Autowired+@Qualifier("userDao") private UserDao userDao;
普通属性注入替代
@Value("itcast") private String driver;
可能你会觉得这样多余了,我直接给属性赋值不就好了吗,
其实这种适用于动态管理,在spring文件中加入配置文件,再通过注解将配置文件中的值赋给属性比如我们之前导入了数据库信息的配置文件我们现在直接将jdbc.driver对应的值赋给driver
@Value("${jdbc.driver}") private String driver;
这里注意使用了注解开发要在配置文件中添加包扫描
<!--配置组件扫描--> <context:component-scan base-package="com.ember"/>
如果都用@Component("")创建bean的话不能直观看到这个bean是属于哪一层,对此spring提供了不同层创建bean的注解(使用都是一样的)
@Repository("userDao") public class UserDaoImpl implements UserDao { ------------------------------------------- @Service("userService") public class UserServiceImpl implements UserService {
Spring新注解
使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:
非自定义的Bean的配置:
加载properties文件的配置:context:property-placeholder
组件扫描的配置:context:component-scan
引入其他文件:
我们就是创建一个spring核心类,来替代spring核心配置文件,同样我们如果配置太多会将配置类分成多个再主核心类通过@import引入
//标致该类是Spring的核心配置类,用于取代核心配置文件 @Configuration //添加包扫描 <context:component-scan base-package="com.ember"/> @ComponentScan("com.ember") /*//加载配置文件 @PropertySource("classpath:jdbc.properties")*/ @Import(DataSourceConfiguration.class) public class SpringConfiguration { }
@PropertySource("classpath:jdbc.properties") public class DataSourceConfiguration { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean("dataSource")//Spring会将当前方法的返回值以指定名称存储到spring容器中 public DataSource getDataSource(){ //创建数据源对象 DruidDataSource dataSource=new DruidDataSource(); //.设置基本连接信息driver url user password dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } }
随后在类中引用核心类创建ApplicationContext对象就可以使用了
public class UserController { public static void main(String[] args) { ApplicationContext app=new AnnotationConfigApplicationContext(SpringConfiguration.class); UserService userService = app.getBean(UserService.class); userService.save(); } }
Spring集成junit
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService as = ac.getBean("accountService",IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
解决方式:
• 让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它
• 将需要进行测试Bean直接在测试类中进行注入
集成步骤:
① 导入spring集成Junit的坐标
<!--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>test</scope> </dependency> <!-- spring基本坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <!-- spring整合junit需要依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency>
② 使用@Runwith注解替换原来的运行期
import java.sql.SQLException; @RunWith(SpringJUnit4ClassRunner.class)
③ 使用@ContextConfiguration指定配置文件或配置类
//@ContextConfiguration("classpath:applicationContext.xml") @ContextConfiguration(classes={SpringConfiguration.class}) public class SpringJunitTest {
④ 使用@Autowired注入需要测试的对象
@Autowired private UserService userService; @Autowired private DataSource dataSource;
⑤ 创建测试方法进行测试
@Test public void test1() throws SQLException { userService.save(); System.out.println(dataSource.getConnection()); } }
AOP
spring理念:无侵入式编程
以下这个例子只有save方法体循环打印了一万次,但是我们执行四个方法发现update和delete方法也打印一万次且输出执行世间,这就是通过切面编程完成的,在不改变源代码的基础上给这两个方法追加了功能
入门案例
导入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <!-- aop依赖包 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> </dependencies>
完成Dao和其实现类
public interface BookDao { public void save(); public void update(); } ---------------------------- @Repository public class BookDaoImpl implements BookDao { @Override public void save() { System.out.println(System.currentTimeMillis()); System.out.println("book dao save ..."); } public void update(){ System.out.println("book dao update ..."); } }
创建通知类
//通知类必须配置成Spring管理的bean @Component //设置当前类为切面类类 @Aspect public class MyAdvice { //设置切入点,要求配置在方法上方 @Pointcut("execution(void com.ember.dao.BookDao.update())") private void pt(){} //设置在切入点pt()的前面运行当前操作(前置通知) // @Before("pt()") public void method(){ System.out.println(System.currentTimeMillis()); } }
在springconfig里要用注解告知我们要使用注解开发AOP——@EnableAspectJAutoProxy
AOP工作流程
AOP切入点表达式
入门案例中我们使用了表达式(描述接口中的方法),也可以描述实现类中的方法
以下是表达式格式
但是项目中会有很多个切入点,这会造成我们要写很多这样的表达式,解决方法如下
要使用这个平时开发就要符合编程规范
AOP通知类型
@Before @After
案例:测量业务层接口万次执行效率
我们以spring整合mybatis的代码为模板,开始AOP开发
引入依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
开启aop功能
//开启aop注解开发功能 @EnableAspectJAutoProxy public class SpringConfig { }
完成通知类
@Component @Aspect public class ProjectAdvice { //切入点配置 //匹配业务层的所有方法 @Pointcut("execution(* com.ember.service.*Service.*(..))") private void servicePt(){} @Around("ProjectAdvice.servicePt()") public void runSpeed(ProceedingJoinPoint pjp) throws Throwable { //获取原始方法信息的封装对象 Signature signature = pjp.getSignature(); //获取方法所在路径 String className = signature.getDeclaringTypeName(); //获取方法名 String methodName = signature.getName(); long start=System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { Object ret=pjp.proceed(); } long end=System.currentTimeMillis(); System.out.println("万次执行 "+className+"."+methodName+"用时"+(end-start)+"ms"); } }
AOP通知获取数据
以下小案例删除传入字符串后的空格
Spring集成Web环境
环境搭建
这里呢就是要搭建tomcat环境和servlet环境搭建
这里我晚上试了有两种都可以,第一种是我们javaweb时用得,在pom中引用tomcat
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.2.0</version> </plugin> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> </plugin> </plugins> </build>
还需要引入servlet依赖
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency>
我们通过注解去设定访问路径(xml太麻烦了)
@WebServlet("/userServlet") public class UserServlet extends HttpServlet {
这样通过右键工程run maven可以成功访问
还有就是我们javaweb没成功的在ide里面配置
这种就只用在pom里添加两个依赖就好了
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.2.1</version> </dependency>
这次成功了!(刚开始两种方法都失败,后来发现是我粘贴过来没粘贴到spring配置文件,同时配置文件里的bean和注入都要写正确才行)
ApplicationContext应用上下文获取方式
我们会发现在我们要调用getbean的时侯都要通过
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
去引入配置文件创造容器,这样有个弊端
应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置文件加载多次,应用上下文对象创建多次。
解决弊端:
在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。
之前我们学javaweb的时候提到过监听器但是没有具体使用,下面我们通过使用深入学习
自定义ContextLoaderListener
这里不用记住,理解就好了,spring底层已经封装好了监听器直接用就好了,我们相当于防着底层自己写一个监听器
为了方便实现,我们创建个包存放所有的监听器
创建类实现ServletContextListener接口
public class ContextLoaderLisener implements ServletContextListener { //初始化方法 @Override public void contextInitialized(ServletContextEvent servletContextEvent) { ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml"); //.将spring的应用上下文对象存储到ServletContext域中 ServletContext servletContext=servletContextEvent.getServletContext(); servletContext.setAttribute("app",app); } //.销毁方法 @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { } }
初始化方法在服务器启动时就会执行,我们将spring的应用上下文对象存储到ServletContext域中,以便我们调用
监听器是要在web xml中声明的
<listener> <listener-class>com.ember.listener.ContextLoaderLisener</listener-class> </listener>
我们现在一般不用xml声明而是直接用注解,在类上面加@WebListener即声明监听器
实现了监听器后,我们的代码就可以改了
@WebServlet("/userServlet") 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(); //也可以用req.getServletContext()获得servletContext对象; ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app"); UserService userService = app.getBean(UserService.class); userService.save(); } }
代码优化
上面我们将配置文件名直接写到监听器中这种写死的以后不好修改,我们可以提到配置文件中,这里我们直接在web.xml文件中定义一个全局初始化参数
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param>
那我们之前的代码就可以改一下不用写死了
@WebListener public class ContextLoaderLisener implements ServletContextListener { //初始化方法 @Override public void contextInitialized(ServletContextEvent servletContextEvent) { ServletContext servletContext=servletContextEvent.getServletContext(); //.读取web.xml中的全局参数 String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation"); ApplicationContext app=new ClassPathXmlApplicationContext(contextConfigLocation); //.将spring的应用上下文对象存储到ServletContext域中 servletContext.setAttribute("app",app); System.out.println("spring容器创建完毕"); }
同样ApplicationContext对象的名字也是我们在监听器中自定义的,那每次我们在servlet中用
servletContext.getAttribute("app");
都要记住其名字
所以我们一般使用一个工具类
public class WebApplicationContextUtils { public static ApplicationContext getWebApplicationContext(ServletContext servletContext){ return (ApplicationContext) servletContext.getAttribute("app"); } }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml"); ServletContext servletContext = this.getServletContext(); //也可以用req.getServletContext()获得servletContext对象; // ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app"); ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext); UserService userService = app.getBean(UserService.class); userService.save(); } }
Spring提供的获取上下文的工具
上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监
听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工
具WebApplicationContextUtils供使用者获得应用上下文对象。
所以我们需要做的只有两件事:
① 在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)
pom:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.0.5.RELEASE</version> </dependency>
web.xml:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
② 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext
@WebServlet("/userServlet") public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext servletContext = this.getServletContext(); ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext); UserService userService = app.getBean(UserService.class); userService.save(); } }
这样我们导入以来后直接使用就好了
Spring整合MyBatis
在web阶段我们知道MyBatis程序与数据库建立连接的核心步骤有哪些(这一步我们一般写到了service层)
其中最核心的就是SqlSessionFactory对象,我们原来是自己写了一个工具类通过调用其get方法快速获取到SqlSessionFactory对象
而spring整合mybatis就是简化这些操作的
其中初始化类型别名和初始化dataSources部分就是在管理sqlsessionfactory对象的
除了我们前面的spring相关依赖包之外,我们还需要导入一些依赖包
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency>
其中mybatis-spring依赖时mybatis专门为了spring整合自己提供的jar包,版本不能乱写是一一对应的
创建三个配置类
首先完成spring核心配置类的配置(注解方式)
//定义spring核心配置类 @Configuration //使用注解方式要扫描包 @ComponentScan("com.ember") //@PropertySource:加载类路径jdbc.properties文件 @PropertySource("classpath:jdbc.properties") //加载其他的配置文件 @Import({com.ember.config.JdbcConfig.class, com.ember.config.MybatisConfig.class}) public class SpringConfig { }
再是jdbc的配置
public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String userName; @Value("${jdbc.password}") private String password; //创建datasources对象,并且给其属性配值,将其存到spring容器中 @Bean public DataSource dataSources() throws PropertyVetoException { ComboPooledDataSource ds=new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(userName); ds.setPassword(password); return ds; } }
其中properties文件存放了数据库信息
这里说明下因为在spring配置类中我们应用了这个配置类,且加载了properties文件
我们才能获取到propertoes文件的信息
最后就是mybaties的配置(重点)
之前我们获取sqlsessionfactory对象,我们是通过工具类现在我们spring提供了快速创建此对象的类
public class MybatisConfig { //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象 @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); //设置类型别名的包(实体类所在包) ssfb.setTypeAliasesPackage("com.ember.domain"); //传入容器中的datasource对象 ssfb.setDataSource(dataSource); return ssfb; } //定义bean,返回MapperScannerConfigurer对象 @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); //映射扫描配置(我们在dao层用mybatis注解完成sql语句就要在这里添加扫描) msc.setBasePackage("com.ember.dao"); return msc; } }
现在呢整合就写完了,现在的dao层就相当于之前的mapper可以通过注解完成方法对应的sql语句
public interface AccountDao { @Insert("insert into tbl_account(name,money)values(#{name},#{money})") void save(Account account); @Delete("delete from tbl_account where id = #{id} ") void delete(Integer id); @Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ") void update(Account account); @Select("select * from tbl_account") List<Account> findAll(); @Select("select * from tbl_account where id = #{id} ") Account findById(Integer id); }
最后就可以写测试类调用方法了
public class App2 { public static void main(String[] args) { //通过配置类创建容器 ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); //创建service层对象,通过它调用方法,返回结果 AccountService accountService = ctx.getBean(AccountService.class); Account ac = accountService.findById(1); System.out.println(ac); } }
事务
有点像是多线程中的同步锁,为此spring提供了一个接口和一个实现类
以下是事务的案例
实体有这些属性
dao,分别是转入转出的操作
@Update("UPDATE tbl_account SET money=money+#{money} WHERE name =#{name}") void inMoney(@Param("name")String name,@Param("money")Double money); @Update("UPDATE tbl_account SET money=money-#{money} WHERE name =#{name}") void outMoney(@Param("name")String name,@Param("money")Double money);
service
/** * 转账操作 * @param out * @param in * @param money */ public void transfer(String out,String in,double money);
@Override public void transfer(String out, String in, double money) { accountDao.outMoney(out,money); //模仿异常 int i=1/0; accountDao.inMoney(in,money); }
测试类
@Test public void testTransfer(){ accountService.transfer("tom","vava",1000); }
当我们不模仿异常的时候能够正常改动
但是当我们模仿了异常在执行发现钱减少了一百,只执行了异常前的操作
这怎么行于是我们给service加上事务
首先去开启事务
//开启事务注解管理器 @EnableTransactionManagement public class SpringConfig {
在jdbcconfig中创建事务管理器
//创建事务管理器 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager=new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }
最后在service的方法上加上事务标志
如果所有方法都要事务标志直接在接口上方定义一次就好了
事务相关配置
就是事务注解的属性和意思
以下通过对上面案例的追加功能,了解其配置
先创建个表
DROP TABLE IF EXISTS tbl_log; CREATE TABLE tbl_log( id int NOT NULL auto_increment, info varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, createDate TIME DEFAULT NULL, PRIMARY KEY (id) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; INSERT INTO tbl_log VALUES (1, 'tom向vava转账1000元',curtime());
public interface LogDao { @Insert("INSERT INTO tbl_log(info,createDate) values (#{info},now())") void log(String info); }
public interface LogService { @Transactional void log(String out,String in,Double money); }
@Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; @Override public void log(String out, String in, Double money) { String info=out+"向"+in+"转账"+money+"元"; logDao.log(info); } }
随后我们在原有的accountServiceImpl里面添加
@Autowired private LogService logService;
@Override public void transfer(String out, String in, double money) { try{ accountDao.outMoney(out,money); int i=1/0; accountDao.inMoney(in,money); }finally { logService.log(out,in,money); } }
但是我们发现出现异常仍然不能完成记录
问题:日志记录和转账操作隶属与同一事务,同成功同失败
就是设置该事务是一个新事务,从原有事务中脱离
总结pom文件依赖
以下是整合了junit mybatis 的spring相关依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现