Spring
spring简介
Spring致力于提供一个以统一的、高效的方式构造整个应用,并且可以将单层框架以最佳的组合揉和在一起建立一个连贯的体系。可以说Spring是一个提供了更完善开发环境的一个框架,可以为POJO(Plain Ordinary Java Object)对象提供企业级的服务。
Spring的核心思想便是IoC和AOP,Spring本身是一个轻量级容器,Spring的组件就是普通的Java Bean,我们只需要编写好Java Bean组件,然后将他们“装配”起来就可以了,组件的初始化和管理均由Sping完成,只需在配置文件中声明即可。这种方式最大的优点是各组件的耦合极为松散
Spring优点
Spring能有效地组织你的中间层对象。
Spring能消除在许多工程中常见的对Singleton的过多使用。
Spring能消除各种各样自定义格式的属性文件的需要,使配置信息一元化。
Spring能够帮助我们真正意义上实现针对接口编程。
Spring支持JDBC和O/R Mapping产品(Hibernate)。
Spring能使用AOP提供声明性事务管理,可以不直接操作JTA也能够对事务进行管理。
通过Spring/IOC
Spring; IOC(inversion of Control 控制反转)/DI(Dependecy Injection 依赖注入),作用是,协调各组件相互的依赖关系,同时大大提高了组件的可移植性。
控制反转模式的基本概念是:不创建对象,但是描述创建他们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器(在Spring框架中是IOC容器)负责将这些联系在一起。
IOC实现方式
1、接口注入:利用接口将调用者与实现者分离
2、设值注入:在类中暴露setter方法来实现依赖关系
3、构造器注入:即通过构造方法完成依赖关系
springIOC容器
Spring容器是Spring框架的核心,使用ioc管理所有spring组件。
SpringIOC容器提供了两种:BeanFactory,ApplicationContext。
ApplicationContext是BeanFactory的子接口,提供配置类、注解等多种方式,实例化Spring容器中管理的组件,并协作对象间的关联关系。
ApplicationContext应用上下文实现类
ClassPathXmlApplicationContext spring使用XML配置文件注册组件,XML文件必须在类路径下。
FileSyestemXmlApplicationContext spring使用XML配置文件注册组件,可以指定相对路径和绝对路径,查找XML文件。
XmlWebApplicationContext spring使用XML配置文件注册组件,根据WEB项目部署路径查找XML文件。
AnnotationConfigApplicationContext spring使用配置类和注解注册组件
springIOC环境搭建
1、导入相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
2、将配置文件applicationContext.xml放入resources目录下
3、定义接口和实现类
public interface IUserDao {
public void upadate();
}
public class UserDaoImpl implements IUserDao {
@Override
public void upadate() {
System.out.println("执行修改");
}
}
4、在配置文件中,注册spring组件
<bean id="userDao" class="com.project.dao.impl.UserDaoImpl"></bean>
5、创建spring容器对象,取出spring组件
//创建应用上下文实例,并读取XML配置文件,加载注册在XML文件中的组件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
需要注意,在spring容器读取配置文件后会对每个已注册的spring组件进行实例化,并完成初始化。如果有一个spring组件初始化失败,启动都会报错。
//按类型匹配,在已注册的spring组件中,查找和该接口匹配的实现类对象
//如果返回的是接口类型,要求该接口只有一个实现类,否则spring无法清楚匹配哪一个实现类
IUserDao dao = context.getBean(IUserDao.class);
dao.upadate();
//按名称匹配,根据注册组件的id得到该组件对象
IUserDao dao = (IUserDao) context.getBean("dao2");
dao.upadate();
spring组件的单实例和多实例
spring容器取出的spring组件,默认是单实例模式的(scope=“singleton”)。无论去多少次都只有一个对象。
如果希望每次从容器去除的spring组件,都是一个新的对象。可以在注册spring组件时,加上scope=“prototype” 属性
<bean id="userDao" class="com.project.dao.impl.UserDaoImpl" scope="prototype"></bean>
spring组件的初始化和销毁
spring容器在创建spring组件,可以通过init-method属性指定的方法,完成组件的初始化动作。
spring容器在销毁spring组件时,可以通过destroy-method属性指定的方法,完成组件销毁动作。
<bean id="userDao" class="com.project.dao.impl.UserDaoImpl" init-method="init" destroy-method="destory"></bean>
装配实体类
在注册spring组件时,可以对该组件的属性进行赋值和装配。在标签中,提供了子标签,可以完成属性的赋值和装配。
<bean id="uservice" class="com.project.service.impl.UserServiceImpl">
<!--初始化普通属性-->
<property name="name" value="tom"></property>
<!--装配已注册的spring组件,name为属性名,ref表示引用已注册spring组件id-->
<property name="userDao" ref="userDao"></property>
</bean>
利用该方式完成属性初始化,用的是set注入。所以属性必须提供set方法。
装配集合/数组
<property name="list">
<list>
<value>1</value>
<value>jack</value>
<ref bean="userDao"></ref>
</list>
</property>
构造器注入
构造器注入需要依赖组件的构造方法,组件提供了构造方法,Spring容器才能通过指定构造方法,完成组件属性的初始化。
<bean id="uservice" class="com.project.service.impl.UserServiceImpl">
<!--index表示构造方法形参的位置,0表示第一个形参-->
<constructor-arg index="0" value="aaa"></constructor-arg>
</bean>
使用index注入属性,无法区分参数个数一致的重载构造方法。
这时,可以用形参名称匹配:
<bean id="uservice" class="com.project.service.impl.UserServiceImpl">
<constructor-arg name="name" value="aaa"></constructor-arg>
</bean>
构造器注入的方式需要依赖于组件的构造方法。而且只在构造方法调用时初始化一次属性。这时,如果在注册组件时,同时添加了属性。由于方式注入属性值调用的是set方法,这在对象产生之后执行。所以,会覆盖构造器中注入的属性值。
使用配置类+注解方式注册spring组件
@Configuration //声明该类为配置类
public class SpringConfig {
@Bean //注册该组件是spring组件
public IUserDao getUserDao() {
return new UserDaoImpl();
}
}
测试代码:
//产生支持配置类+注解方式的上下文对象,传入配置类类模板
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
IUserDao dao = context.getBean(IUserDao.class);
dao.add();
如果接口有多个实现类,这时需要按名称进行匹配
@Bean("ud") //注册组件时,加上该组件名称,相当于id属性
public IUserDao getUserDao() {
return new UserDaoImpl();
}
测试类中,按名称获取
IUserDao userDao = (IUserDao) context.getBean("ud");
userDao.add();
从spring容器中取出的组件,默认为单例模式,如果要设置每次取出的是新的组件对象,可以通过设置@Scope(“prototype”)进行,默认为@Scope(“singleton”)
@Bean("ud") //注册该组件是spring组件
@Scope("prototype")
public IUserDao getUserDao() {
return new UserDaoImpl();
}
每个组件都在配置类中进行注册,过于繁琐。Spring提供了注解方式,对Spring组件进行注册。
Spring中,通过@Component、@Repository、@Service、@Controller对Spring组件进行注册。
@Component 注册通过的Spring组件
@Repository 用于注册Spring持久组件
@Service 用于注册Spring业务组件
@Controller 用于注册Spring控制器组件
定义Spring组件类
//@Component
@Repository
public class RoomDaoImpl implements IRoomDao {
@Override
public void find() {
System.out.println("执行查询");
}
}
在配置类中扫描指定包中的Spring组件类
@Configuration //声明该类为配置类
@ComponentScan("com.project.dao")// 扫描指定包及子包中的spring组件
public class SpringConfig {
}
在Spring组件类中,如果需要使用另一个spring组件时,可以用@Autowired注入
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private IUserDao userDao;
}
使用@Autowired注入spring组件时,会按类型进行匹配。找到和该接口匹配的实现类,再进行注入。但是,如果该接口有多个实现类,那么spring容器无法知道如何注入该属性,就会报错。这时,需要使用名称进行匹配。
注册spring组件时,加上名称,相当于id属性。
@Repository("userDao2")
public class UserDao2 implements IUserDao {
@Override
public void add() {
System.out.println("UserDao2添加方法");
}
}
在引用该spring组件时,可以用@Qualifier(“”),说明该组件使用哪个spring组件进行注入。
@Service
public class UserServiceImpl implements IUserService {
@Autowired
@Qualifier("userDao2")
private IUserDao userDao;
}
SpringAOP
AOP称为面向切面编程,是一种通过预编译方式和运行期间,动态代理实现程序功能的统一维护的技术。
AOP面向切面编程,主要使用代理模式实现。在目标对象执行目标方法时,添加非功能性逻辑。
Spring AOP对动态代理进行了封装,Spring AOP框架分散在系统中的功能块放到一个地方-----切面。
业务模块只包含核心功能,辅助功能转到切面中,使其更加清晰。
SpringAOP术语
切面(ASpect):就是你要实现的交叉的非核心业务功能
连接点(Joinpoint):应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出·········
通知(Advice):通知切面的实际实现代码
切入点(Pointcut):定义通知应用在哪些连接点
目标对象(Target):被通知的对象
代理(Proxy):将通知应用到目标对象后创建的对象
织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程
通知
通知包含了切面逻辑,创建通知对象就是编写实现交叉功能的代码。
通知分为:
前置通知:目标方法执行前,执行非功能性业务。
后置通知:目标方法执行后,执行非功能性业务。
坏绕通知:目标方法执行前,执行后,都需要执行非功能性业务。
抛出异常通知:目标方法抛出异常后,执行非功能性业务。
AOP环境搭建
1、导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
2、创建目标接口和目标类
public interface IUserService {
public void speak();
}
@Service
public class UserServiceImpl implements IUserService {
@Override
public void speak() {
System.out.println("执行说话业务");
}
}
3、创建配置类
@Configuration
@ComponentScan("com.project")
@EnableAspectJAutoProxy //配置自动代理
public class SpringConfig {
}
4、创建切面类
@Component
@Aspect //声明该类为切面类
public class SpringAspect {
//@Before 表示哪些目标方法执行前需要执行该方法
//execution(* com.project.service..*.*(..)) 表示应用范围
//第一个* 表示返回类型 com.project.service.表示指定包中
//com.project.service..表示指定包及子包
//第二个* 表示该包中的任意类 第三个*表示该类中任何方法
//(..)表示任何方法参数
@Before("execution(* com.project.service..*.*(..))")
public void before() {
System.out.println("前置通知");
}
}
5、测试
public class AopTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
IUserService service = context.getBean(IUserService.class);
service.speak();
}
}
//执行结果为:
// 前置通知
// 执行说话业务
后置通知
//返回后通知,目标方法执行后执行
@AfterReturning("execution(* com.project.service..*.*(..))")
public void after() {
System.out.println("后置通知");
}
目标方法如果有异常发生,则返回后通知不会执行。
//后通知,无论目标方法执行时,是否有异常发生,后通知都会执行
@After("execution(* com.project.service..*.*(..))")
public void after() {
System.out.println("后置通知");
}
环绕通知
//坏绕通知,目标方法执行前执行后都需要执行非功能性业务
@Around("execution(* com.project.service..*.*(..))")
public Object around(ProceedingJoinPoint point) {
System.out.println("环绕前");
//得到目标对象
Object target = point.getTarget();
try {
//执行目标对象的目标方法,得到目标方法执行后的返回值
Object returnObj = point.proceed();
System.out.println("环绕后");
return returnObj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
连接池
在传统开发中,客户端每次请求服务器,服务器进行数据库操作时,都会建立连接,数据库操作执行完成后,都会关闭连接,以减少服务器内存消耗。由于建立连接和关闭连接都是流操作,频繁的流操作会增加额外的开销,导致响应速度下降。同时,由于对于客户端的请求都会创建连接,不能控制连接创建的数量。一旦并发量大的时候,连接会无休止的分配,会导致服务器内存崩溃。
工作原理
为数据库连接建立一个“缓冲池”,这就是连接池。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,该连接的状态由“空闲”变为“忙碌”。使用完毕之后再放回去,该连接的状态由“忙碌”变为“空闲”。由于连接一直不关闭,没有建连接和关连接造成的额外开销。通过设定连接池最大连接数来防止系统无休止的数据库连接。
工作流程
当持久层申请一个连接对象时,从连接池中取出一个空闲连接分配给持久层。如果连接池中没有空闲连接,就看连接数量有没有达到最大连接数。如果没有,那么创建一个新连接对象,分配给持久层。如果达到最大连接数,就会等待一段时间,在这点时间内,如果有连接释放,就分配给等待用户。如果时间到了以后,还没有连接释放,则返回null。
spring整合mybatis
Spring整合mybatis是将Spring和Mybatis应用到一个项目中。
Mybatis提供数据库相关的操作,完成对象数据和关系数据的转换。
Spring完成项目的管理,通过IOC和AOP完成依赖注入,事务管理等操作。
1、导入依赖
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring-version>5.3.20</spring-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
</dependencies>
2、建立实体类、业务接口、mapper接口
public class StudentBean {
private Integer id;
private String name;
private LocalDate birthday;
private String phone;
}
public interface IStudenrService {
public void add(StudentBean studentBean);
public List<StudentBean> findAll();
}
public interface IStudentMapper {
@Insert("insert into t_student(s_name,s_birthday,s_phone) values (#{name},#{birthday},#{phone})")
public void add(StudentBean studentBean);
@Select("select * from t_student")
@ResultMap("studentMap")
public List<StudentBean> findAll();
}
3、导入mybatis配置文件
Mybatis.cfg.xml放在resources根目录
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<typeAliases>
<package name="com.project.bean"/>
</typeAliases>
</configuration>
在resources目录中,创建com/project/mapper目录,放入mapper文件
<mapper namespace="com.project.mapper.IStudentMapper">
<resultMap id="studentMap" type="StudentBean">
<id property="id" column="pk_studentId"></id>
<result property="name" column="s_name"></result>
<result property="birthday" column="s_birthday"></result>
<result property="phone" column="s_phone"></result>
</resultMap>
</mapper>
4、创建配置类
@Configuration
@ComponentScan("com.project")
@MapperScan("com.project.mapper")//扫描指定包中的mapper文件
@EnableTransactionManagement//允许使用注解管理事务
public class MyBatisConfig {
/**
* 设置数据源
*
* @return 数据源
*/
@Bean
public DataSource getDataSource() {
//创建连接池对象
DruidDataSource dataSource = new DruidDataSource();
//设置驱动类
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//设置URL
dataSource.setUrl("jdbc:mysql://localhost:3306/db?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false&allowMultiQueries=true");
//mysql登陆用户名
dataSource.setUsername("root");
//mysql登陆密码
dataSource.setPassword("yks957");
//设置最大连接
dataSource.setMaxActive(50);
//设置最小连接
dataSource.setMinIdle(10);
//设置超时时间
dataSource.setMaxWait(1000);
return dataSource;
}
/**
* 设置会话工厂
*
* @return 会话工厂
*/
@Bean
public SqlSessionFactoryBean getFactoryBean() {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//设置数据源
factoryBean.setDataSource(this.getDataSource());
//设置mybatis主配置文件
factoryBean.setConfigLocation(new ClassPathResource("mybatis.cfg.xml"));
return factoryBean;
}
/**
* 得到事务管理器
*
* @return 事务管理器
*/
@Bean
public TransactionManager getTransaction() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
//设置数据源
transactionManager.setDataSource(this.getDataSource());
return transactionManager;
}
}
5、书写业务接口实现类
@Service
@Transactional //该类中所有方法,支持事务管理
public class StudentServiceImpl implements IStudenrService {
@Autowired
private IStudentMapper mapper;
@Override
public void add(StudentBean studentBean) {
mapper.add(studentBean);
}
@Override
public List<StudentBean> findAll() {
return mapper.findAll();
}
}
6、测试
public class StudentTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
IStudenrService service = context.getBean(IStudenrService.class);
service.add(new StudentBean("李华", LocalDate.parse("2000-01-02"), "18272382711"));
System.out.println(service.findAll());
}
}
mybatis分页插件
1、添加分页插件依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
2、在mybatis主配置文件中,注册分页插件,同时指定数据库方言
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
3、创建业务接口方法
public PageInfo<StudentBean> cutAll(int pageNO);
public PageInfo<StudentBean> cutByItem(int pageNO, String name, LocalDate startDate, LocalDate endDate);
4、创建mapper接口方法
public List<StudentBean> cutByItem(@Param("name") String name,@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
5、书写mapper文件
<resultMap id="studentMap" type="StudentBean">
<id property="id" column="pk_studentId"></id>
<result property="name" column="s_name"></result>
<result property="birthday" column="s_birthday"></result>
<result property="phone" column="s_phone"></result>
</resultMap>
<select id="cutByItem" resultMap="studentMap">
select *
from t_student
where 1 = 1
<if test="name!=null and name!=''">
and s_name like "%"#{name}"%"
</if>
<if test="startDate !=null">
and s_birthday>=#{startDate}
</if>
<if test="endDate!=null">
<![CDATA[
and s_birthday<=#{endDate}
]]>
</if>
</select>
6、书写业务接口实现类
public PageInfo<StudentBean> cutAll(int pageNO) {
PageHelper.startPage(pageNO, 3);
PageInfo<StudentBean> pageInfo = PageInfo.of(mapper.findAll());
return pageInfo;
}
@Override
public PageInfo<StudentBean> cutByItem(int pageNO, String name, LocalDate startDate, LocalDate endDate) {
PageHelper.startPage(pageNO, 3);
PageInfo<StudentBean> pageInfo = PageInfo.of(mapper.cutByItem(name, startDate, endDate));
return pageInfo;
}
7、测试
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
IStudentService service = context.getBean(IStudentService.class);
// System.out.println(service.cutAll(2));
System.out.println(service.cutByItem(1, "张", LocalDate.parse("2000-01-01"), LocalDate.parse("2010-01-01")));
}