1. Spring简介
Spring的特点:
- 方便解耦,简化开发
- AOP编程的支持
- 声明式事务的支持
- 方便程序的测试
- 方便集成各种优秀框架
- 降低Java EE API的使用难度
Spring的优点:Spring的目标是使已存在的技术更加易用
- 低侵入式设计,代码污染极低
- 独立于各种应用服务器
- Spring的DI机制降低了业务对象替换的复杂性,提高了组件之间的解耦
- Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用
- Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问
- Spring并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部
Spring体系结构:
名词解释:
- IoC(Inversion of Control):控制反转,是面向对象编程中的依赖倒转原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做DI(Dependency Injection,依赖注入)。
- AOP(Aspect Oriented Programming):面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
- ORM(Object Relational Mapping):对象关系映射,用于实现面向对象编程语言里不同类型系统的数据之间的转换。
- OXM(Object XML Mapping):指数据实体对象与XML节点之间的映射。
- SpEL(Spring Expression Language):Spring表达式语言
2. Spring中的控制反转-IoC*
自定义IoC的思路(使用简单工厂模式解耦):加载工厂类时解析xml文件,获取所有bean标签的id属性和class属性,根据class属性创建类的对象,并放入到容器Map(id,object)中,对外提供一个根据id在容器里取对象的静态方法。
Spring中的控制反转:由Spring框架创建和管理对象,需要使用时,直接从Spring的工厂(容器)里取。
Spring中的IoC使用:
-
创建Maven工程,添加相关坐标*
org.springframework spring-context 5.0.2.RELEASE org.springframework spring-jdbc 5.0.2.RELEASE org.aspectj aspectjweaver 1.8.7 org.springframework spring-test 5.0.2.RELEASE junit junit 4.12 test org.mybatis mybatis-spring 1.3.0 org.mybatis mybatis 3.5.3 mysql mysql-connector-java 5.1.47 org.projectlombok lombok 1.18.10 provided log4j log4j 1.2.12 org.slf4j slf4j-api 1.6.6 org.slf4j slf4j-log4j12 1.6.6 -
准备接口和实现类
// 接口 public interface PojoDao { void update(); } // 实现类 public class PojoDaoImpl implements PojoDao { public void init() { System.out.println("PojoDaoImpl.init()"); } @Override public void update() { System.out.println("PojoDaoImpl.update()"); } public void destroy() { System.out.println("PojoDaoImpl.destroy()"); } }
-
在resources下创建Spring的XML配置文件(任意名称,如:applicationContext.xml),配置bean标签*
-
创建Spring的核心容器来获取bean*
import com.liu2m.dao.PojoDao; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { @Test public void test01() { // 1. 创建Spring的核心容器ApplicationContext // 创建核心容器时,就读取了整个配置文件,并将bean标签对应的对象创建出来放到核心容器中了 // ClassPathXmlApplicationContext:从类的根路径下加载xml配置文件 // FileSystemXmlApplicationContext:从磁盘路径上加载xml配置文件 // AnnotationConfigApplicationContext:用注解配置容器对象时使用 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); // XmlBeanFactory是老版本使用的工厂,目前已被废弃 // BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); // 2. 调用核心容器的方法,根据id获取对象 PojoDao pojoDao = (PojoDao) applicationContext.getBean("pojoDao"); // 使用对象 pojoDao.update(); // PojoDaoImpl.update() } }
ApplicationContext和BeanFactory的区别:
- ApplicationContext是BeanFactory的子接口
- BeanFactory的加载方式只有懒加载
- ApplicationContext的加载方式有懒加载和非懒加载
- 各种ApplicationContext和BeanFactory的实现都用到了模板方法模式
实例化Bean的方式:
-
使用无参构造来实例化Bean*
-
使用工厂类来实例化Bean
// 创建工厂类 public class PojoDaoFactory { // 方法需要是静态的 public static PojoDao createPojoDaoImpl() { return new PojoDaoImpl(); } }
-
使用工厂对象来实例化Bean
// 创建工厂类 public class PojoDaoFactory { // 方法需要是非静态的 public PojoDao createPojoDaoImpl() { return new PojoDaoImpl(); } }
3. Spring中的依赖注入-DI*
Spring中的依赖注入:Spring在创建类的对象时,顺便给对象的属性赋值(注入)。
环境准备:
public interface PojoService {
void update();
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PojoServiceImpl implements PojoService {
private PojoDao pojoDao;
private String simple;
private String[] array;
private Map map;
@Override
public void update() {
pojoDao.update();
System.out.println(this);
}
}
实现依赖注入的方式:
-
使用构造方法注入+:
0 1 -
使用set方法注入*:
0 1 -
使用p命名空间注入-:
4. Spring中的面向切面编程-AOP*
OOP针对业务处理过程中的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
AOP针对业务处理过程中的某个步骤或阶段进行抽象封装,以获得逻辑过程中各部分之间低耦合性的隔离效果。
AOP的主要意图:将业务逻辑代码中重复的行为抽取出来,进而改变这些行为的时候不影响业务逻辑的代码。
Spring中AOP的实现原理就是动态代理,Spring会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
AOP中的术语:
- JoinPoint:连接点(所有可以被增强的方法),在Spring的AOP中,指业务层类的所有现有的方法
- Pointcut:切入点(真正需要增强的方法)
- Advice:通知/增强(具体用于增强的代码)
- 前置通知:在目标方法执行前执行
- 后置通知:在目标方法执行完也没有抛出异常时执行,可以得到被增强方法的返回值
- 异常通知:在目标方法抛出异常时执行,可以获得异常的信息
- 最终通知:在目标方法执行之后无论有没有异常都执行
- 环绕通知:在目标方法之前和之后执行,可以阻止目标方法执行
- Aspect:切面(切入点与通知间的关系,让通知和切入点进行关联)
开发阶段可以先编写核心业务代码;然后把公用代码抽取出来制作成切面中的通知;最后在配置文件中声明切面。Spring框架在运行阶段会监控切入点方法的执行,一旦监控到切入点方法被执行,就使用动态代理创建目标对象的代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,完成完整的代码逻辑运行。
常用切入点表达式:
任意public方法:execution(public * *(..))
任意以set开头的方法:execution(* set*(..))
某个类的任意方法:execution(* 类的全限定名.*(..))
某个包下任意类的任意方法(不含子包):
1. execution(* 包的全限定名.*.*(..))
2. within(包的全限定名.*)
某个包下任意类的任意方法(包含子包):
1. execution(* 包的全限定名..*.*(..))
2. within(包的全限定名..*)
实现当前接口的类的任意方法:
1. this(接口的全限定名)
2. target(接口的全限定名)
只有一个参数且实现了Serializable的任意方法:args(java.io.Serializable)
只有一个参数且参数有Classified注解的任意方法:@args(Classified注解的全限定名)
有Transactional注解的方法:
1. @target(org.springframework.transaction.annotation.Transactional)
2. @within(org.springframework.transaction.annotation.Transactional)
3. @annotation(org.springframework.transaction.annotation.Transactional)
指定名称bean下的任意方法:
1. bean(名称)
2. bean(*名称)
使用配置文件配置Spring的AOP:
-
定义需要被增强的业务逻辑类:
/** * 目标: * 1. 在执行CRUD方法之前,使用前置通知before * 2. 在执行CRUD方法之后,若没有抛出异常则使用后置通知after-returning * 3. 在执行CRUD方法抛出异常之后,使用异常通知after-throwing * 4. 在执行CRUD方法之后无论有没有抛出异常,均使用最终通知after * 5. 使用环绕通知around,计算Update方法的执行时间 */ public class PojoServiceImpl implements PojoService { @Override public void create() { System.out.println("PojoServiceImpl.create()"); } @Override public void retrieve() { System.out.println("PojoServiceImpl.retrieve()"); } @Override public void update() { try { Thread.sleep(3000); } catch (Exception e) { e.printStackTrace(); } System.out.println("PojoServiceImpl.update()"); } @Override public void delete() { System.out.println("PojoServiceImpl.delete()"); int exception = 1 / 0; } }
-
定义切面类:
public class PermissionAspect { public void useBefore() { System.out.println("PermissionAspect.useBefore()"); } public void useAfterReturning() { System.out.println("PermissionAspect.useAfterReturning()"); } public void useAfterThrowing() { System.out.println("PermissionAspect.useAfterThrowing()"); } public void useAfter() { System.out.println("PermissionAspect.useAfter()"); } public void useAround(ProceedingJoinPoint joinPoint) { long start = System.currentTimeMillis(); try { // 执行目标方法 joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("useAround():end - start = " + (end - start)); } }
-
在Spring的配置文件中配置AOP:
-
单元测试:
import com.liu2m.service.PojoService; 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(locations = "classpath:applicationContext.xml") public class SpringTest { // 自动注入要使用的对象 @Autowired private PojoService pojoService; @Test public void test01() { System.out.println("---------pojoService.create():一起正常---------"); pojoService.create(); System.out.println("---------pojoService.retrieve():一切正常---------"); pojoService.retrieve(); System.out.println("---------pojoService.update():计算执行时间---------"); pojoService.update(); System.out.println("---------pojoService.delete():抛出异常---------"); pojoService.delete(); } }
5. Spring的JdbcTemplate-
JdbcTemplate对原生的JDBC进行了简单的封装
使用步骤:
导入坐标
-
传递DataSource创建JdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
定义SQL语句:SQL的参数使用?作为占位符
-
调用JdbcTemplate的方法来执行SQL
update():执行DML增删改语句
queryForMap():将列名作为key,将值作为value,将这条记录封装为一个map集合。这个方法查询的结果集长度只能是1
queryForList():将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
query():查询结果,将结果封装为JavaBean对象;query的参数是RowMapper,一般可以使用其实现类BeanPropertyRowMapper<类型>(类型.class)完成数据到JavaBean的自动封装。其中的思想是策略模式,RowMapper是抽象策略,其每一个实现类都是一个具体策略。
queryForObject():查询结果,将结果封装为对象。一般用于聚合函数的查询
JdbcTemplate使用示例:
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class JdbcTemplateTest {
// 1. 传递DataSource创建JdbcTemplate对象
// 使用自定义的DruidUtils获取数据库连接池对象
private static JdbcTemplate jdbcTemplate = new JdbcTemplate(DruidUtils.getDataSource());
// 查询所有记录,将其封装为User对象的List集合
@Test
public void test01() {
// 2. 定义SQL语句:SQL的参数使用?作为占位符
String sql = "select * from user";
// 3. 调用JdbcTemplate的方法来执行SQL
List userList = jdbcTemplate.query(sql, new RowMapper() {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user = new User();
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
double balance = resultSet.getDouble("balance");
user.setId(id);
user.setUsername(username);
user.setPassword(password);
user.setBalance(balance);
return user;
}
});
for (User user : userList) {
System.out.println(user);
}
}
// 查询所有记录,将其封装为User对象的List集合
@Test
public void test02() {
// 2. 定义SQL语句:SQL的参数使用?作为占位符
String sql = "select * from user";
// 3. 调用JdbcTemplate的方法来执行SQL
List userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class));
for (User user : userList) {
System.out.println(user);
}
}
}
6. Spring管理事务
PlatformTransactionManager:Spring为事务定义了一个统一的接口,其实现类就是Spring真正管理事务的对象
- 如果Dao层使用的是JDBC、JdbcTemplate、MyBatis,则可以使用DataSourceTransactionManager来处理事务
- 如果Dao层使用的是Hibernate,则可以使用HibernateTransactionManager来处理事务
TransactionDefinition:定义了事务的隔离级别、传播行为、超时信息等
TransactionStatus:定义事务是否是新事务、是否有保存点等
6.1 编程式事务-
编程式事务使用:
- 创建事务管理者,并传入数据源
- 创建事务模板,并传入事务管理者
- 通过事务模版处理事务
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private DataSource dataSource;
@Override
public void transfer(String fromName, String toName, double money) {
// 1. 创建事务管理者,并传入数据源
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
// 2. 创建事务模板,并传入事务管理者
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
// 3. 通过事务模版处理事务
transactionTemplate.execute(new TransactionCallback
6.2 声明式事务*
Spring声明式事务的作用:无论Spring集成什么Dao框架,事务代码都不需要再编写了。声明式事务的思想就是使用AOP,以面向切面的方式完成事务的管理。
事务的传播特性解决的问题:如果Service层的某个方法中除了调用Dao层的方法,还调用了本类其他的Service方法,那么在调用其他Service方法时,必须保证两个Service处在同一个事务中,确保事物的一致性。
使用xml配置文件配置声明式事务:
7. Spring常用注解*
注解使用前要在配置文件中配置包扫描
配置文件方便维护,注解方便使用
建议:自己写的类用注解进行IoC,不是自己写的类用配置文件进行IoC;类用什么进行的IoC,属性就用什么进行DI
7.1 IoC相关注解
以下四个注解都可以对类进行IoC,但相应的类上最好用相应的注解:
- @Controller:一般用在Web层的各种Controller类上
- @Service:一般用在Service层的各种Service类上
- @Repository:一般用在DAO层的各种Dao类上(MyBatis没有Dao实现类,所以这个注解基本不用)
- @Component:一般用在除了三层结构的其他类上
- 以上注解的value属性就是id,如果不指定value属性,则id为类名首字母小写
scope的注解:@Scope("prototype"),默认值为singleton
lazy-init的注解:@Lazy(false),默认值为true
init-method的注解:@PostConstruct,作用在方法上
destroy-method的注解:@PreDestroy,作用在方法上
7.2 DI相关注解
注解注入属性使用的是暴力反射,set方法可以省略
注入简单类型:@Value("值")
注入对象类型:建议只有一个实现类时使用@Autowired,有多个实现类时使用@Resource
- @Autowired:按照类型自动注入其他bean对象
- 如果Spring核心容器里只有一个该类型对象,则可以自动注入
- 如果Spring核心容器里有多个该类型对象,则找id和属性名一致的对象注入,找不到就抛异常
- @Qualifier("bean的id"):在@Autowired的基础之上,再按照bean的id注入;必须和@Autowired一起使用,不能独立使用,但给方法参数注入时,可以独立使用
- @Resource(name = "bean的id"):能够进行自动装配以及手动装配;如果一个接口有多个实现类,可以使用@Resource指定找具体的某一个实现
7.3 AOP相关注解
@Aspect:指定该类是切面类
@Pointcut("切入点表达式"):作用在方法上
@Before("切入点"):前置通知
@AfterReturning("切入点"):后置通知
@AfterThrowing("切入点"):异常通知
@After("切入点"):最终通知
@Around("切入点"):环绕通知
使用注解配置Spring的AOP:
定义需要被增强的业务逻辑类,并使用注解对其进行IoC
-
定义切面类:
@Component @Aspect public class PermissionAspect { @Pointcut("execution(* com.liu2m.service.impl.PojoServiceImpl.*(..))") public void point1() { } @Pointcut("execution(* com.liu2m.service.impl.PojoServiceImpl.update(..))") public void point2() { } @Before("point1()") public void useBefore() { System.out.println("PermissionAspect.useBefore()"); } @AfterReturning("point1()") public void useAfterReturning() { System.out.println("PermissionAspect.useAfterReturning()"); } @AfterThrowing("point1()") public void useAfterThrowing() { System.out.println("PermissionAspect.useAfterThrowing()"); } @After("point1()") public void useAfter() { System.out.println("PermissionAspect.useAfter()"); } @Around("point2()") public void useAround(ProceedingJoinPoint joinPoint) { long start = System.currentTimeMillis(); try { // 执行目标方法 joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("useAround():end - start = " + (end - start)); } }
-
在Spring的配置文件中加载AOP的注解驱动:
7.4 声明式事务相关注解
@Transactional(属性 = 值, 属性 = 值):
- 作用在方法上:指定该方法需要使用事务
- 作用在类上:指定该类所有方法都需要使用事务
使用注解配置声明式事务:
- 在Spring的xml配置文件中配置事务管理者并加载事务注解驱动
- 在要使用事务的方法上添加@Transactional注解
7.5 配置相关注解
component-scan的注解:@ComponentScan(basePackages = "com.liu2m")
@Configuration:指定当前类是一个Spring配置类,获取核心容器时需要的是AnnotationConfigApplicationContext(类.class)
@Import({其他配置类的.class字节码对象}):引入其他配置类,在引入其他配置类时,可以不用再写@Configuration注解
@PropertySource({"classpath:properties文件的位置"}):加载.properties文件中的配置
@Bean(name = "当前方法创建出的对象的id"):只能写在方法上,表示将此方法返回的对象放入到Spring容器中
8. Spring整合其他框架
8.1 Spring整合数据库连接池
整合后Dao层使用时,可直接注入dataSource
Spring整合C3P0连接池:
-
导入连接池坐标
c3p0 c3p0 0.9.1.2 -
配置数据源对象dataSource
Spring整合Druid连接池:
-
导入连接池坐标
com.alibaba druid 1.1.18 -
配置数据源对象dataSource
Spring整合HikariCP连接池:
-
导入连接池坐标
com.zaxxer HikariCP 3.1.0 -
配置数据源对象dataSource
8.2 Spring整合MyBatis*
整合后可以不写MyBatis的核心配置文件,使用时直接根据id从Spring核心容器中获取PojoDao的代理对象
导入坐标后,使用Spring配置文件来整合*:
-
在resources下创建jdbc.properties:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db01?useSSL=false jdbc.username=root jdbc.password=123456
-
在resources下创建整合mybatis的配置文件applicationMyBatis.xml:
-
在Spring配置文件中导入整合mybatis的配置文件:
-
使用:
@Test public void test01() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); // 从核心容器中获取PojoDao的代理对象 PojoDao pojoDao = (PojoDao) applicationContext.getBean("pojoDao"); // 使用pojoDao System.out.println(pojoDao.findAll()); }
导入坐标后,使用纯注解来整合-:
-
创建MyBatis配置类:
import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.datasource.DriverManagerDataSource; import javax.sql.DataSource; public class MyBatisConfig { @Bean public DataSource getDataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/db01?useSSL=false"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } @Bean public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); sqlSessionFactoryBean.setTypeAliasesPackage("com.liu2m.pojo"); return sqlSessionFactoryBean; } @Bean public MapperScannerConfigurer getMapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.liu2m.dao"); return mapperScannerConfigurer; } }
-
创建Spring配置类:
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; // 创建一个配置类 @Configuration @ComponentScan(basePackages = "com.liu2m") @Import({MyBatisConfig.class}) public class SpringConfig { }
-
使用:
@Test public void test01() { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); // 从核心容器中获取PojoDao的代理对象 PojoDao pojoDao = (PojoDao) applicationContext.getBean("pojoDao"); // 使用pojoDao System.out.println(pojoDao.findAll()); }
8.3 Spring整合JUnit-
整合后可以不用创建核心容器,自动注入使用:junit的版本必须是4.12及以上
import com.liu2m.dao.PojoDao;
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(locations = "classpath:applicationContext.xml")
public class SpringTest {
// 自动注入要使用的对象
@Autowired
private PojoDao pojoDao;
@Test
public void test01() {
// 直接使用pojoDao
System.out.println(pojoDao.findAll());
}
}