Spring5 详解

1 概述

  • Spring 是轻量级的开源的JavaEE框架
  • Spring 可以解决企业应用开发的复杂性
  • Spring 有两个核心部分:IOC 和 AOP
  • IOC:控制反转,把创建对象过程交给 Spring 容器进行管理
  • AOP:面向切面,不修改源代码进行功能增强

Spring 特点

  • ①方便解耦,简化开发
    ②AOP 编程支持
    ③方便程序测试
    ④方便和其他框架进行整合
    ⑤方便进行事物操作
    ⑥降低 API 开发难度

2 IOC

  • 控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理
  • 使用IOC目的:为了降低耦合度

2.1 IOC 实现原理

xml解析、工厂模式、反射
对于<bean id="dao" class="com.hy.UserDao"/>配置来说,有如下动作:↓

class UserFactory {
    public static UserDao getDao() {
        String classValue = class属性值; // 由xml解析获得,获得类的全路径名
        Class clazz = Class.forName(classValue); // 由反射获取类对象
        return (UserDao)clazz.newInstance(); // 由类对象进行创建新的对象
    }
}

IOC思想基于IOC容器完成,IOC容器底层就是对象工厂 Factory
在这里插入图片描述

  • Spring提供IOC容器实现两种方式:(两个接口)
    BeanFactory
     是Spring内部的使用接口,不提供给开发人员进行使用
    加载配置文件时,不会创建里面的对象,而是在使用时才创建对象
    ApplicationContext
     是BeanFactory接口的子接口,提供了更多功能,一般由开发人员使用
  • 加载配置文件时,就会把配置文件中的对象进行创建

2.2 bean 注入

Bean管理包含两个操作Spring创建对象、Spring注入属性
①Spring创建对象

  • 在Spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建
    <bean id="user" class="com.hy.User"/>

②Spring注入属性

  • set注入
    <property name="name" value="张三"/>
  • 构造器注入
    <constructor-arg name="name" value="张三"/>
  • p命名空间注入
    xmlns:p="http://www.springframework.org/schema/p"
    <bean id="user" class="com.hy.User" p:name="张三"/ >
    注入属性时可以为属性注入null值
    <property name="name"><null/></property>
    如果属性中含有特殊符号(…处为需要转义的字符串),两种方法
    使用< >进行转义
    <property name="name"><value><![CDATA[......]]></value></property>
  • 注入外部bean
    <property name="userDao" ref="userDaoImpl"/>

可以把注入集合的部分提取出来

  • 在spring配置文件中引入命名空间util
  • 使用util标签完成list集合注入提取
<util:list id="listBall">
    <value>羽毛球</value>
    <value>乒乓球</value>
    <value>足球</value>
</util:list>

<bean id="user" class="com.hy.bean.User">
    <property name="list" ref="listBall"/>
</bean>

2.3 bean 的类型

  • Spring有两种类型的bean,一种是普通bean,另一种是工厂bean (FactoryBean)
    普通bean:在配置文件中定义bean的类型就是返回类型
    工厂bean:在配置文件中定义bean的类型可以和返回类型不一样

2.4 bean 的作用域

  • 在spring里面,可以设置创建bean实例是单实例(默认)还是多实例
    <bean>标签中,属性 scope 用于指定单实例还是多实例
  • singleton:单例,整个容器中只有一个对象实例,并且在加载配置文件时就创建,因为是单例的,类似于饿汉式单例模式
  • prototype:多例,每次获取bean都产生一个新的对象,但并不是在加载配置文件时创建,而是在getBean()方法调用时创建
    当然scope属性值不止这些,还有request、session,但并不常见

2.5 bean 的生命周期

如果使用了bean后置处理器,则会有7步,否则就5步

  1. 通过构造器创建bean实例(无参构造)
  2. 为bean的属性设置值和对其他bean的引用(调用set方法)
    [把bean实例传递bean后置处理器的postProcessBeforeInitialization()方法]
  3. 调用bean的初始化方法(需要进行配置初始化方法)
    [把bean实例传递bean后置处理器的postProcessAfterInitialization()方法]
  4. bean可以使用了(即对象已经获取到了)
  5. 当容器关闭时,调用bean的销毁方法(需要进行配置销毁方法)

下面来测试一下 bean 的各个阶段

<bean id="orders" class="com.hy.beanCycle.Orders" init-method="initMethod" destroy-method="destroyMethod">
    <property name="oname" value="手机"/>
</bean>
public class Orders {
    private String oname;
    public Orders() {
        System.out.println("无参构造执行了,创建bean实例");
    }
    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("set方法执行了,设置成员变量值");
    }
    // 创建执行的初始化方法
    public void initMethod() {
        System.out.println("初始化方法执行了");
    }
    // 创建销毁方法
    public void destroyMethod() {
        System.out.println("销毁方法执行了");
    }
}
public class BeanCycleTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans6.xml");
        Orders orders = context.getBean("orders", Orders.class);
        //手动将bean销毁
        context.close();
    }
}

在这里插入图片描述
现在我们测试带后置处理器的 bean 生命周期

<!--配置后置处理器,会为所有bean配置后置处理器-->
<bean id="myBeanPost" class="com.hy.beanCycle.MyBeanPost"/>
public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后置处理器的postProcessBeforeInitialization()方法执行了");
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后置处理器的postProcessAfterInitialization()方法执行了");
        return bean;
    }
}

在这里插入图片描述

2.6 自动装配

对于实体类,可以进行自动装配

public class Emp {
    private Dept dept;
    public void setDept(Dept dept) {
        this.dept = dept;
    }
}
<bean id="emp" class="com.hy.bean.Emp" autowire="byType"></bean>
<bean id="dept" class="com.hy.bean.Dept">
    <property name="dname" value="Tom"/>
</bean>
  • byName:根据实体类中的属性名,与已装配的bean的id进行比较(即dept),找到则注入成功
  • byType:根据实体类中的属性类型,与已装配的bean的class进行比较(即Dept),找到则注入成功
  • 注意:被注入的属性仍需自己装配

2.7 引入外部属性文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=

在 spring 配置文件中配置,这里我们使用的是 Spring 自带的数据源

<!-- 引入外部属性文件,注意需要导入context 外部命名空间 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</bean>

2.8 注解开发

  • 要想使用注解开发,需要引入 aop 依赖
  • jar包:
    spring-aop-5.2.6.RELEASE.jar
  • Maven:
<dependency>
	<groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
  • 并开启注解扫描(在 spring 配置文件中配置)
<context:component-scan base-package="com.hy.entity"/>

2.8.1 创建对象的注解

①@Component:用在任意地方
②@Service:用在业务层
③@Controller:用在web层,控制层
④@Repository:用在dao层(持久层)

  • 以上四个注解功能是一样的,都可以用来创建bean实例
@Component(value = "user") // 与<bean id="user" class="com.hy.entity.User"/>等价
public class User {
    public void hello(){
        System.out.println("hello");
    }
}
  • 如果不指定value,默认是类名首字母小写user
  • 可以自定义扫描哪个注解,下面是只扫描entity包下的所有Controller注解
<context:component-scan base-package="com.hy.entity" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

也可以自定义不扫描哪个注解,下面是扫描entity包下的所有除Controller以外的注解

<context:component-scan base-package="com.hy.entity">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

2.8.2 注解方式进行属性注入

  • @AutoWired根据属性类型进行自动注入,byType,是Spring包中提供的
  • 第一步,将service和dao对象创建,在service和dao类添加创建对象的注解Service和Repository
@Repository
public class UserDaoImpl implements UserDao { }

@Service
public class UserService { }
  • 第二步,在service注入dao对象,在service添加dao类型属性,在属性上面使用AutoWired注解
@Service
public class UserService {
    @Autowired
    private UserDao userDao;
}

这时,Service层就可以使用userDao对象了

  • @Qualifier:根据属性名称进行注入,byName(在一个接口有多个实现类的情况下使用)
  • 需要和AutoWired一起使用
@Repository(value = "userDaoImpl")
public class UserDaoImpl implements UserDao { }

@Service
public class UserService {
    @Autowired
    @Qualifier(value = "userDaoImpl")
    private UserDao userDao;
}
  • @Resource:可以根据类型注入,也可以根据名称注入是java扩展包提供的
@Resource //根据类型注入
@Resource(name = "userDaoImpl") //根据名称注入
private UserDao userDao;
  • @Value:注入普通类型属性
@Value("Tom")
private String name;

此时的 name 值就为 Tom 了

2.8.3 完全注解开发(一般在SpringBoot中使用)

  • 创建配置类,替代xml配置文件
@Configuration // 将当前类设置为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.hy"}) // 开启注解扫描
public class SpringConfig { }
  • 测试类
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.hello();

3 AOP

  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率
  • 通俗点说:在不修改源代码的基础上进行功能的添加、增强
    在这里插入图片描述

3.1 AOP底层原理

  • 底层使用了动态代理,有两种情况:
    ①有接口情况下,使用JDK动态代理,创建接口实现类的代理对象,进行增强类中的方法
    ②没接口情况下,使用CGLIB动态代理,创建子类的代理对象,进行增强类中的方法

3.2 jdk动态代理

  • 使用Proxy类里面的newProxyInstance()方法创建代理类的实例

参数:

  • ClassLoader:类加载器
  • interfaces:增强方法所在的类,这个类所实现的接口,支持多个接口
  • InvocationHandler:实现这个接口InvocationHandler,创建代理对象,写增强方法
public static void main(String[] args) {
    Class[] interfaces = {UserDao.class};
    // 创建接口实现类的代理对象
    UserDao proxy = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),
        interfaces, new UserDaoProxy(new UserDaoImpl()));
    proxy.add();
}

// 创建代理类
class UserDaoProxy implements InvocationHandler{
    // 把创建的是谁的代理对象,把谁传递过来,即UserDaoImpl
    // 有参构造传递
    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("方法之前执行...");
        // 被增强的方法执行
        Object res = method.invoke(obj, args);
        // 方法之后
        System.out.println("方法之后执行...");
        return res;
    }
}

原有的接口实现类:

public class UserDaoImpl implements UserDao{
    @Override
    public void add() {
        System.out.println("add方法正在执行...");
    }

    @Override
    public void update() {
        System.out.println("update方法正在执行");
    }
}

运行后可发现原有接口中的方法已被增强,但并未改变原有接口实现类的代码,这就是 AOP 的本质

3.3 相关术语

  • ①连接点:类中的哪些方法可以被增强,那么这些方法就称为连接点
  • 切入点:实际被增强的方法,这些方法称为切入点
  • ③**通知(增强)**:实际被增强的逻辑部分称为通知(增强)
    通知的种类:
    前置通知:在方法前执行
    后置通知:在方法后执行
    环绕通知:在方法前后均执行
    异常通知:抛出异常后执行
    最终通知:类似于finally,一定会执行
  • 切面:是一个动作,把通知应用到切入点的过程

3.4 AOP 的使用

Spring 框架一般都是基于 AspectJ 实现 AOP 操作,AspectJ 不是 Spring 组成部分,是独立的 AOP 框架,一般把 AspectJ 和 Spring 框架一起使用,进行 AOP 操作

3.4.1 xml方式

配置切入点表达式,用于指定对哪个类里面的哪个方法进行增强
语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))

<bean id="book" class="com.hy.aopxml.Book"></bean>
<bean id="bookProxy" class="com.hy.aopxml.BookProxy"></bean>

<aop:config>
    <aop:pointcut id="cut" expression="execution(* com.hy.aopxml.Book.buy(..))"/>
    <aop:aspect ref="bookProxy">
        <aop:before method="before" pointcut-ref="cut"></aop:before>
    </aop:aspect>
</aop:config>

3.4.2 注解方式

①在spring配置文件中,开启注解扫描
②使用注解创建 User 和 UserProxy 对象
③在增强类上面添加注解 @Aspect
④在 spring 配置文件中开启生成代理对象

<aop:aspectj-autoproxy/>

⑤在代理类(增强类)中配置通知

// 前置通知
@Before(value = "execution(* com.hy.aopanno.User.add(..))")
public void before(){
    System.out.println("before......");
}
// 后置通知
@After(value = "execution(* com.hy.aopanno.User.update(..))")
public void after(){
    System.out.println("after......");
}
// 环绕通知
@Around(value = "execution(* com.hy.aopanno.User.delete(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("around:before......");
    proceedingJoinPoint.proceed(); // 如果不执行此方法,那么原方法是不会执行的
    System.out.println("around:after......");
}
@AfterThrowing(value = "execution(* com.hy.aopanno.User.select(..)))")
public void afterThrowing(){
    System.out.println("afterThrowing......");
}
@AfterReturning(value = "execution(* com.hy.aopanno.User.other(..)))")
public void afterReturning(){
    System.out.println("afterReturning......");
}
  • 如果有相同的切入点,可以对相同切入点进行抽取
@Pointcut(value = "execution(* com.hy.aopanno.User.add(..)))")
public void pointDemo(){ }
  • 在需要使用相同切入点时配置相应的方法即可
@Before(value = "pointDemo()")
public void before(){
    System.out.println("before......");
}
  • 当有多个增强类对同一个方法进行增强,则可以设置增强类的优先级
  • 在增强类上面添加注解@Order(数字类型值),值越小优先级越高,优先执行值小的增强类
@Order(1)
public class UserProxy1 { }

@Order(3)
public class UserProxy2 { }
  • 同样可以使用完全注解开发,创建配置类
@Configuration
@ComponentScan(basePackages = {"com.hy"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig { }

4 JDBCTemplate

  • Spring框架对JDBC进行封装,使用JDBCTemplate方便实现对数据库操作,当然日后开发我们不会使用JDBCTenplate,而是使用Mybatis进行集成,使用其中的SqlSession核心对象进行操作

使用步骤:
①引入相关依赖
 spring-orm-5.2.6.RELEASE
 spring-jdbc-5.2.6.RELEASE
 spring-tx-5.2.6.RELEASE
 mysql-connector-java-5.1.47

②在spring配置文件配置数据库连接池,这里使用阿里巴巴的德鲁伊连接池,使用前需要引入依赖

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    <property name="url" value="jdbc:mysql://user_db"/>
    <property name="username" value="root"/>
    <property name="password" value=""/>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
</bean>

③配置JdbcTemplate对象,注入DataSource

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入dataSource-->
    <property name="dataSource" ref="dataSource"/>
</bean>

④创建service类,创建dao类,在dao注入jdbcTemplate对象

@Repository
public class BookDaoImpl implements BookDao {
    //注入jdbcTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

@Service
public class BookService {
    @Autowired
    private BookDao bookDao;
}

⑤进行增删改操作(三者做法类似)

public void insertUser(User user) {
    String sql = "insert into t_user values(?, ?, ?)";
    jdbcTemplate.update(sql, user.getUser_id(), user.getUsername(), user.getUstatus());
}

public void updateUser(User user) {
    String sql = "update t_user set username=?, ustatus=? where user_id=?";
    jdbcTemplate.update(sql, user.getUsername(), user.getUstatus(), user.getUser_id());
}

public void deleteUserById(int id) {
    String sql = "delete from t_user where user_id=?";
    jdbcTemplate.update(sql, id);
}

⑥进行查询操作

  • 查询某个值
    在这里插入图片描述
    requiredType:sql语句的返回值类型
public int selectCountFromUser() {
    String sql = "select count(*) from t_user";
    Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
    return integer;
}
  • 查询某个对象
    在这里插入图片描述
    rowMapper:是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
public User selectUserById(int id) {
    String sql = "select * from t_user where user_id=?";
    User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
    return user;
}
  • 查询集合
    在这里插入图片描述
public List<User> selectAllUser() {
    String sql = "select * from t_user";
    List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
    return users;
}

⑦批量添加

public void batchInsert(List<Object[]> args) {
    String sql = "insert into t_user values(?, ?, ?)";
    jdbcTemplate.batchUpdate(sql, args);
}

5 事物

  • 一般将事物添加到三层结构里的Service层,即业务逻辑层
  • 使用Spring进行事务管理操作有两种方式:编程式事物、声明式事物
    编程式事物较为繁琐,需要在代码中手动进行编程并配置
    声明式事物实现简单,做出相应的配置即可
  • Spring的声明式事物底层使用的是AOP
  • Spring事物管理API:提供了一个PlatformTransactionManager接口,代表事务管理器,这个接口针对不同的框架提供了不同的实现类

声明式事物管理
我们先看基于注解的方式:

  • ①在Spring配置文件中配置事物管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="dataSource"/>
</bean>
  • ②引入tx名称空间
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
  • ③开启事物注解,传入事物管理器
<tx:annotation-driven transaction-manager="transactionManager"/>
  • ④在service类或其中的方法上,添加事物注解 @Transactional
    如果添加到类上面,这个类里面所有方法都会添加事物
    如果添加到方法上面,只为这个方法添加事物
@Service
@Transactional
public class UserService { }

相关参数配置:
在这里插入图片描述
propagation:事物传播行为

  • 多个事物方法之间进行调用,这个过程中事物是如何进行管理的
  • spring定义了7个传播行为:required 和 requires_new 较为常见
  • required:如果有事物在运行,当前的方法就在这个事物内运行,否则就启动一个新的事物,并在自己的事物内运行
  • requires_new:当前的方法必须启动新事物,并在它自己的事物内运行,如果有实物正在运行,应该将它挂起
  • supports、not_supported、mandatory、never、nested

isolation:事物隔离级别

  • 多事物操作之间产生的影响,脏读、不可重复读、幻读
  • 脏读:一个事物读取了另一个事物中未提交的数据
  • 不可重复读:在一个事物中,两次读取的数据不一致,因为其他事物进行了修改操作
  • 幻读:在一个事物中,读取到另一提交事物所添加的数据
  • 四个隔离级别:
  • 读未提交:READ_UNCOMMITTED
  • 读已提交:READ_COMMITTED
  • 可重复读:REPEATABLE_READ
  • 串行化:SERIALIZABLE

timeout:超时时间

  • 事物需要在一定时间内提交,如果不提交则进行回滚
  • 默认值-1,设置时间是以秒为单位进行计算

readOnly:是否只读

  • 默认值false,表示可以查询和增删改
  • 设置为true后,只能查询

rollbackFor:回滚

  • 设置出现哪些异常进行事物回滚

noRollbackFor:不回滚

  • 设置出现哪些异常不进行事物回滚

继续看基于 xml 配置文件方式:
①配置事物管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="dataSource"/>
</bean>

②配置通知

<tx:advice id="txadvice">
    <!--配置事物相关参数-->
    <tx:attributes>
        <!--指定哪种规则的方法上面添加事物-->
        <tx:method name="accountMoney"/>
    </tx:attributes>
</tx:advice>

③配置切入点和切面

<aop:config>
    <!--配置切入点-->
    <aop:pointcut id="cut" expression="execution(* com.hy.service.User1Service.accountMoney(..))"/>
    <!--配置切面-->
    <aop:advisor pointcut-ref="cut" advice-ref="txadvice"/>
</aop:config>

我们也可以像spring一样,完全注解开发

  • 创建配置类,使用配置类替代xml配置文件
@Configuration // 配置类
@ComponentScan(basePackages = "com.hy") // 注解扫描
@EnableTransactionManagement // 开启事物
public class TxConfig {
    // 创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/springtest");
        dataSource.setUsername("root");
        dataSource.setPassword("");
        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;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sadness°

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值