本文根据尚硅谷王老师的Spring5视频教程总结的。z作者跟着教程全程实践,本文内容较为简略,详细内容请移步王老师视频教程。
目录
一、概述
轻量级开源的JavaEE框架
核心部分:
(1)IOC:控制反转,将创建对象交给Spring管理
(2)AOP:面向切面,不修改源代码进行功能增强
IOC基本包:
二、IOC
控制反转:将创建对象和对象之间的调用过程交给Spring进行管理
使用目的:降低代码耦合度
1、IOC底层原理
xml解析、工厂模式、反射
第一步:xml配置文件,配置创建的对象(bean对象)
第二步:创建工厂类
2、IOC接口(BeanFactory)
IOC基于IOC容器完成,IOC容器底层即对象工厂
IOC容器实现:
(1)BeanFactory:加载配置文件时不会创建对象,在获取对象(使用)时才会去创建对象 (内部使用接口)
(2)ApplicationContext:加载配置文件时就会把配置文件中的对象创建 (开发人员使用)
3、IOC操作Bean管理
Bean管理:
(1)创建对象
(2)注入属性
基于xml方式
创建对象:
在配置文件中添加bean标签,标签中添加对应属性,实现对象创建
注入属性:
(1)使用set方法注入
类中创建对应属性set()方法—>配置文件中配置属性注入
/**
* 演示使用set方法注入属性
*/
public class Book {
//创建属性
private String bname;
private String bauthor;
private String adderss;
//创建属性对应的set方法
public void setBname(String bname){
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
public void setAddress(String address) {
this.adderss = address;
}
public void testDemo(){
System.out.println(bname+" "+bauthor+" "+adderss);
}
}
<!-- 2 set方法注入属性-->
<bean id="book" class="com.atguigu.spring5.Book">
<!-- 使用property完成属性注入 -->
<property name="bname" value="易筋经"></property>
<property name="bauthor" value="达摩老祖"/>
<!-- 属性值为空 -->
<!--<property name="address"><null></null></property>-->
<!-- 属性值包含特殊字符 -->
<property name="address">
<value>
<![CDATA[<<南京>>]]>
</value>
</property>
</bean>
(2)使用有参构造函数注入
类中创建对应属性有参构造方法—>配置文件中属性注入
public class Orders {
//属性
private String oname;
private String address;
//有参构造
public Orders(String oname, String address) {
this.oname = oname;
this.address = address;
}
public void testOrders(){
System.out.println(oname+" "+address);
}
}
<!-- 3 有参构造注入属性 -->
<bean id="orders" class="com.atguigu.spring5.Orders">
<constructor-arg name="oname" value="abc"></constructor-arg>
<constructor-arg name="address" value="Chain"></constructor-arg>
</bean>
注入外部属性(默认set()方法注入)
类中属性为另一个类(如service中注入dao)
创建两个类service和dao—>seivice中属性为dao—>为dao创建set()方法—>配置类中创建service和dao对象—>在service的bean对象中注入dao对象
public class UserService {
//创建UserDao类型属性,生成set方法
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
public void add(){
System.out.println("service add....................");
userDao.update();
//原始方式
//UserDao userDao = new UserDaoImpl();
//userDao.update();
}
}
<!-- 1 service和dao对象创建 -->
<bean id="userService" class="com.atguigu.spring5.service.UserService">
<!--2 注入UserDao对象
name属性:类里面属性名称
ref属性:创建userDao对象bean标签id值-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
注入内部属性(默认set()方法注入)
<!-- 内部bean -->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
<!-- 先设置两个普通的属性 -->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!-- 设置对象类型属性 -->
<property name="dept">
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
注入集合属性(默认set()方法注入)
创建类,定义数组、list、map、set类型属性,生成set()方法—>在配置文件中进行配置
<!-- 集合类型属性注入 -->
<bean id="stu" class="com.atgui
<!-- 数组类型属性注入 -->
<property name="course">
<array>
<value>java课程</valu
<value>数据库课程</value
</array>
</property>
<!-- list类型属性注入 -->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<!-- map类型属性注入 -->
<property name="maps">
<map>
<entry key="JAVA" v
<entry key="PHP" va
</map>
</property>
<!-- set类型属性注入 -->
<property name="set">
<set>
<value>MySQL</value
<value>Redis</value
</set>
</property>
<!-- 注入List集合类型,值是对象 -->
<property name="courseList"
<list>
<ref bean="course1"
<ref bean="course2"
</list>
</property>
</bean>
<!-- 创建多个course对象 -->
<bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
<property name="cname" value="Spring5框架"></property>
</bean>
<bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
<property name="cname" value="MyBatis框架"></property>
</bean>
基于注解方式
注解格式:@注解名称(属性名称=属性值,属性名称=属性值…)
使用目的:简化xml配置
Bean管理注解:@Controller、@Service、@Repository、@Component
创建对象:
引入依赖—>开启组件扫描—>在类上面添加注解—>组件扫描细节
<!-- 开启组件扫描
1 如果扫描多个包,使用逗号隔开
2 扫描包上层目录
-->
<context:component-scan base-package="com.atguigu.spring5.aopanno"></context:component-scan>
//在注解里面value可以省略不写,默认是类名称首字母小写 UserService->userService
//@Component(value = "userService") //类似于xml文件中定义<bean id="userService" class=".."/>
@Service
//@Controller
//@Repository
public class UserService {}
属性注入:
@Autowired:根据属性类型进行自动装配
@Qualifier:根据名称进行注入
@Resource:根据属性类型或者名称进行注入
@Value:注入普通类型属性
创建service和dao对象—>在service和dao上添加注解—>开启组件扫描
在service中添加dao属性—>在属性上使用注入注解
完全注解开发
创建配置类代替xml配置文件
@Configuration //作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.atguigu"})
public class SpringConfig { }
FactoryBean
普通bean:配置文件中定义的bean类型就是返回类型
工厂Bean:配置文件中定义的bean类型和返回类型不一样
(1)创建类,让其作为工厂类,实现接口FactoryBean
(2)实现接口里面的方法,在实现的方法中定义返回的bean类型
bean作用域
默认情况下,bean是单实例对象—意思是创建的多个实例对象,其指向的地址是一个
设置多实例对象—只需在配置文件bean标签中设置scope属性为prototype
区别:
单实例在加载配置文件时就会创建单实例对象
多实例在调用getBean方法是创建多实例对象
外部属性文件
(1)配置数据库信息
配置德鲁伊连接池—>引入德鲁伊的依赖jar包
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/userDb" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="username" value="root" />
<property name="password" value="123" />
</bean>
(2)引入外部properties属性文件到配置文件中
引入conetxt名称空间—>配置文件中使用标签引入外部属性文件
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
三、AOP
面向切面编程,可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低。即:通过不修改源代码的方式,在主干功能里边添加新功能
1、AOP底层原理
使用动态代理
(1)有接口,使用JDK动态代理
创建接口实现类代理对象,增强类的方法
(2)无接口,使用CGLIB动态代理
创建当前类子类的代理对象,增强类的方法
JDK动态代理
(1)使用Proxy类中的方法创建代理对象
方法中的三个参数分别是类加载器、增强方法所在的类、实现这个接口的InvocationHandler
(2)编写JDK动态代理代码
创建接口,定义方法—>创建接口实现类,实现方法—>使用Proxy类创建接口代理对象—>创建代理对象代码
// 创建接口实现类代理对象
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao)Proxy. newProxyInstance (JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1, 2);
System. out .println("result:"+result);
}
}
// 创建代理对象代码
class UserDaoProxy implements InvocationHandler {
//1 把创建的是谁的代理对象,把谁传递过来
// 有参数构造传递
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
// 增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法之前
System. out .println("方法之前执行...."+method.getName()+" :传递的参 数..."+ Arrays. toString (args));
// 被增强的方法执行
Object res = method.invoke(obj, args);
// 方法之后
System. out .println("方法之后执行...."+obj); return res;
}
}
2、AOP术语
连接点:类里面哪些方法可以被增强,这些方法称为连接点
切入点:实际被增强的方法
通知(增强):实际增强的逻辑部分 (分类为前置、后置、环绕、异常、最终通知)
切面:把通知应用到切入点的过程
3、AOP操作(准备工作)
一般地,通过AspectJ和Spring框架一起使用进行AOP操作
基于AspectJ实现方式:xml和注解
引入AOP相关依赖
切入点表达式:知道对哪个类的哪个方法进行增强: execution(* [返回类型] [类全路径] 方法名称 ) :例:execution(* com.atguigu.dao.BookDao.add(…))
4、AOP操作(AspectJ注解)
—>创建类,在类里面定义方法
—>创建增强类(编写增强逻辑)
—>进行通知的配置
开启注解扫描->@Component注解上面创建的类->在增强类上添加注解@Aspect->开启Aspect生成代理对象
<!-- 开启 Aspect 生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
—>配置不同类型的通知
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
@Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
—>相同的切入点的抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
—>有多个增强类对同一方法进行增强,设置增强类优先级
增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
—>完全使用注解开发
@Configuration
@ComponentScan(basePackages = {"com.atguigu"}) @EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop { }
四、JdbcTemplate
Spring框架对JDBC进行封装,使用JdbcTemplate实现对数据库的操作
准备工作
—>引入相关jar包
—>配置文件中配置组件扫描和数据库连接池dataSource
—>配置JdbcTemplate对象,注入dataSource
—>创建service类和dao类,在dao中注入jdbcTemplate对象
操作数据库
//添加数据
//1 创建 sql 语句
String sql = "insert into t_book values(?,?,?)";
//2 调用方法实现
Object[] args = {book.getUserId(), book.getUsername(), book.getUstatus()};
int update = jdbcTemplate.update(sql,args);
//修改数据
String sql = "update t_book set username=?,ustatus=? where user_id=?";
Object[] args = {book.getUsername(), book.getUstatus(),book.getUserId()};
int update = jdbcTemplate.update(sql, args);
//删除数据
String sql = "delete from t_book where user_id=?";
int update = jdbcTemplate.update(sql, id);
//查询表记录
String sql = "select count ( * ) from t_book";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
//查询返回对象
String sql = "select * from t_book where user_id=?";
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
//查询返回集合
String sql = "select * from t_book";
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
//批量添加
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values(?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
//批量添加测试
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3","java","a"};
Object[] o2 = {"4","c++","b"};
Object[] o3 = {"5","MySQL","c"};
batchArgs.add(o1); batchArgs.add(o2); batchArgs.add(o3);
bookService.batchAdd(batchArgs);
//批量修改
public void batchUpdateBook(List<Object[]> batchArgs) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System. out .println(Arrays. toString (ints));
}
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"java0909","a3","3"};
Object[] o2 = {"c++1010","b4","4"};
Object[] o3 = {"MySQL1111","c5","5"};
batchArgs.add(o1); batchArgs.add(o2); batchArgs.add(o3);
bookService.batchUpdate(batchArgs);
//批量删除
public void batchDeleteBook(List<Object[]> batchArgs) {
String sql = "delete from t_book where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System. out .println(Arrays. toString (ints));
}
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3"}; Object[] o2 = {"4"};
batchArgs.add(o1); batchArgs.add(o2);
bookService.batchDelete(batchArgs);
五、事务管理
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
特性(ACID):原子性、一致性、隔离性、持久性
Spring声明式事务管理(注解)
—>配置文件中配置事务管理器并引入名称空间tx
<! -- 创建事务管理器 -- >
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
—>开启事务注解
<! -- 开启事务注解 -- >
<tx:annotation-driven transactionmanager="transactionManager"></tx:annotation-driven>
—>在所需的类或方法上添加事务注解@Transactional
—>事务管理参数配置
propagation:事务传播行为
ioslation:事务隔离级别,多事务操作之间不会产生影响,解决:脏读、不可重复读、虚(幻)读
timeout:超时时间
readOnly:是否只读
rollbackFor:回滚
noRollbackFor:不回滚
完全注解开发
@Configuration // 配置类
@ComponentScan(basePackages = "com.atguigu") // 组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// 创建 JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
// 到 ioc 容器中根据类型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入 dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
六、Spring5新特性
1、日志封装
引入jar包—>创建 log4j2.xml配置文件
<? xml version="1.0" encoding="UTF-8" ?>
<! -- 日志级别以及优先级排序 : OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -- >
<! -- Configuration 后面的 status 用于设置 log4j2 自身内部的信息输出,可以不设置, 当设置成 trace 时,可以看到 log4j2 内部各种详细输出 -- >
<configuration status="INFO">
<! -- 先定义所有的 appender -- >
<appenders>
<! -- 输出日志信息到控制台 -- >
<console name="Console" target="SYSTEM_OUT">
<! -- 控制日志输出的格式 -- >
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %5level %logger{36} - %msg%n"/> </console>
</appenders>
<! -- 然后定义 logger ,只有定义 logger 并引入的 appender , appender 才会生效 -- >
<! -- root :用于指定项目的根日志,如果没有单独指定 Logger ,则会使用 root 作为 默认的日志输出 -- >
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
2、@Nullable 注解
@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空
3、JUnit5
引入测试依赖jar包—>使用注解创建测试类
@RunWith(SpringJUnit4ClassRunner.class) // 单元测试框架
@ContextConfiguration("classpath:bean1.xml") // 加载配置文件
//使用一个复合注解替代上面两个注解
//@SpringJUnitConfig(locations = "classpath:bean1.xml")
public class JTest4 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
以上是JUnit4,使用JUnit5只需在导入@Test换成5的即可
4、Webflux
Spring5 添加新的模块,用于 web 开发的,功能和 SpringMVC 类似的,Webflux 使用 当前一种比较流程响应式编程出现的框架
使用传统 web 框架,比如 SpringMVC,这些基于 Servlet 容器,Webflux 是一种异步非阻 塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的
异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同 步,如果发送请求之后不等着对方回应就去做其他事情就是异步
阻塞和非阻塞针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻 塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞
实例:
第一步 创建 SpringBoot 工程,引入 Webflux 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
第二步 配置启动端口号
server.port=8081
第三步 创建包和相关类
//实体类
public class User {
private String name;
private String gender;
private Integer age;
public User(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
//用户操作接口
public interface UserService {
//根据ID查询用户
Mono<User> getUserById(int id);
//查询所有用户
Flux<User> getAllUser();
//添加用户
Mono<Void> saveUserInfo(Mono<User> user);
}
@Repository
public class UserServiceImpl implements UserService {
//创建Map集合存储数据
private final Map<Integer,User> users = new HashMap<>();
public UserServiceImpl(){
this.users.put(1,new User("lucy","nan",20));
this.users.put(2,new User("mary","nv",30));
this.users.put(3,new User("jack","nv",50));
}
//根据ID查询
@Override
public Mono<User> getUserById(int id) {
return Mono.justOrEmpty(this.users.get(id));
}
//查询多个用户
@Override
public Flux<User> getAllUser() {
return Flux.fromIterable(this.users.values());//取出集合
}
//添加
@Override
public Mono<Void> saveUserInfo(Mono<User> userMono) {
return userMono.doOnNext(person ->{ //uesrMono中的值放到person中
//向Map集合中放值
int id = users.size()+1;
users.put(id,person);
}).thenEmpty(Mono.empty());//清空里边的值
}
}
@RestController
public class UserController {
@Autowired
private UserService userService;
//id查询
@GetMapping("/user/{id}")
public Mono<User> getUserId(@PathVariable int id){
return userService.getUserById(id);
}
//查询所有
@GetMapping("/user")
public Flux<User> getUsers(){
return userService.getAllUser();
}
//添加
@PostMapping("/saveuser")
public Mono<Void> saveUser(@RequestBody User user){
Mono<User> userMono = Mono.just(user);
return userService.saveUserInfo(userMono);
}
}