Spring 5笔记(下)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Spring 5笔记

三、AOP


1. 底层原理


面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗来说就是在不修改代码的情况下添加新的功能。


底层通过动态代理来实现:


  • 第一种:有接口的情况,使用JDK动态代理:创建接口实现类的代理对象。


  • 第二种:无接口的情况,使用CGLIB动态代理:创建当前类子类的代理对象。 JDK动态代理举例:


通过 java.lang.reflect.Proxy类 的 newProxyInstance方法 创建代理类。


newProxyInstance方法:


  • 参数一:类加载器


  • 参数二:所增强方法所在的类,这个类实现的接口,支持多个接口


  • 参数三:实现InvocationHandle接口,重写invoke方法来添加新的功能


代码举例:


public interface UserDao {
    public int add(int a, int b);
    public int multi(int a, int b);
}


public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        return a+b;
    }
    @Override
    public int multi(int a, int b) {
        return a*b;
    }
}


public class Main {
    @Test
    public void test1(){
        //所需代理的类实现的接口,支持多个接口
        Class[] interfaces = {UserDao.class};
        UserDao userDao = new UserDaoImpl();
    //调用newProxyInstance方法来创建代理类
        UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(Main.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        int result = userDaoProxy.add(1, 2);
        System.out.println(result);
    }
    //创建内部类,实现InvocationHandler接口,重写invoke方法,添加新功能
    class UserDaoProxy implements InvocationHandler {
        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 invoke = method.invoke(obj, args);
            System.out.println("方法原先的内容执行完了");
            return invoke;
        }
    }
}


2. 基于AspectJ实现AOP操作


(1)AOP相关术语:


  • 连接点:类中可以被增强的方法,称为连接点。


  • 切入点:实际被增强的方法,称为切入点。


  • 通知:增强的那一部分逻辑代码。通知有多种类型:


  • 前置通知:增强部分代码在原代码前面。


  • 后置通知:增强部分代码在原代码后面。


  • 环绕通知:增强部分代码既有在原代码前面,也有在原代码后面。


  • 异常通知:原代码发生异常后才会执行。


  • 最终通知:类似与finally那一部分


  • 切面:指把通知应用到切入点这一个动作。


网络异常,图片无法展示
|


(2)基于AspectJ实现AOP有两种方式:


基于xml配置文件 基于注解方法


(3)切入点表达式


语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])


举例1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强


execution(* com.auguigu.dao.BookDao.add(..))


举例2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强


execution(* com.atguigu.dao.BookDao.*(..))


举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强


execution(* com.atguigu.dao.*.* (..))


(1)基于注解方式


@Component
public class User {
    public void add(){    
        System.out.println("User.add()");
    }
}


@Component
@Aspect   //使用Aspect注解
public class UserProxy {
    //前置通知
    @Before(value="execution(* com.oymn.spring5.User.add(..))")
    public void before(){
        System.out.println("UserProxy.before()");
    }
    //后置通知
    @AfterReturning(value="execution(* com.oymn.spring5.User.add(..))")
    public void afterReturning(){
        System.out.println("UserProxy.afterReturning()");
    }
    //最终通知
    @After(value="execution(* com.oymn.spring5.User.add(..))")
    public void After(){
        System.out.println("UserProxy.After()");
    }
    //异常通知
    @AfterThrowing(value="execution(* com.oymn.spring5.User.add(..))")
    public void AfterThrowing(){
        System.out.println("UserProxy.AfterThrowing()");
    }
    //环绕通知
    @Around(value="execution(* com.oymn.spring5.User.add(..))")
    public void Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("UserProxy.Around()   _1");
        //调用proceed方法执行原先部分的代码
        proceedingJoinPoint.proceed();
        System.out.println("UserProxy.Around()   _2");
    }
}


配置xml文件:


<!--开启组件扫描-->
<context:component-scan base-package="com.oymn"></context:component-scan>
<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>


测试类:


@Test
public void test2(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    User user = context.getBean("user", User.class);
    user.add();
}


运行结果中没有出现异常通知,在add方法中添加int i = 1/0;


public void add(){
    int i = 1/0;
    System.out.println("User.add()");
}


运行结果:从这里也可以看到,但出现异常时,After最终通知有执行,而AfterReturning后置通知并没有执行。


对于上面的例子,有很多通知的切入点都是相同的方法,因此,可以将该切入点进行抽取:通过@Pointcut注解


@Pointcut(value="execution(* com.oymn.spring5.User.add(..))")
public void pointDemo(){
}
//前置通知
@Before(value="pointDemo()")
public void before(){
    System.out.println("UserProxy.before()");
}


设置增强类优先级:


当有多个增强类对同一方法进行增强时,可以通过 @Order(数字值)来设置增强类的优先级,数字越小优先级越高。


@Aspect
@Order(1)
public class PersonProxyva
@Component
@Aspect
@Order(1)
public class PersonProxy


完全注解开发 :


可以通过配置类来彻底摆脱xml配置文件:


@Configuration
@ComponentScan(basePackages = "com.oymn.spring5")
//@EnableAspectJAutoProxy注解相当于上面xml文件中配置的 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy(proxyTargetClass = true)  
public class Config {
}


(2)基于xml方式


这种方式开发中不怎么用,了解即可。


创建Book和BookProxy类


public class Book {
    public void buy(){
        System.out.println("buy()");
    }
}


public class BookProxy {
    public void before(){
        System.out.println("before()");
    }
}


配置xml文件:


<!--创建对象-->
    <bean id="book" class="com.oymn.spring5.Book"></bean>
    <bean id="bookProxy" class="com.oymn.spring5.BookProxy"></bean>
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="p" expression="execution(* com.oymn.spring5.Book.buy(..))"/>
        <!--配置切面-->
        <aop:aspect ref="bookProxy">
            <aop:before method="before" pointcut-ref="p"/>  <!--将bookProxy中的before方法配置为切入点的前置通知-->
        </aop:aspect>
    </aop:config>


四、JdbcTemplate


Spring对JDBC进行封装,使用JdbcTemplate方便对数据库的操作。 (1)增删改操作:


int update(String sql, Object... args);


(2)查询:返回某个值


T queryForObject(String sql,Class<T> requiredType);


(3)查询:返回某个对象


T queryForObject(String sql,RowMapper<T> rowMapper,Object ... args);


(4)查询:返回集合


List<T> query(String sql,RowMapper<T> rowMapper,Object... args);


(5)批量增删改:


int[] batchUpdate(String sql,List<Object[]> batchArgs);


举例:


引入相关jar包


配置数据库连接池;配置JdbcTemplate对象


<context:component-scan base-package="com.oymn"></context:component-scan>
    <!--配置数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/book" />
        <property name="username" value="root" />
        <property name="password" value="000000" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>
    <!--创建JdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入数据库连接池-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>


创建Service类和Dao类,在Dao类中注入JdbcTemplate对象


public interface BookDao {
        public void add(Book book);  //添加图书
        public void update(Book book);  //修改图书
        public void delete(int id);  //删除图书
        public int queryCount();   //查询数量
        public Book queryBookById(int id);  //查询某本书
        public List<Book> queryBooks();   //查询所有书
        public void batchAddBook(List<Object[]> books);  //批量添加图书
        public void batchUpdateBook(List<Object[]> books);  //批量修改图书
        public void batchDeleteBook(List<Object[]> args);  //批量删除图书
    }


@Repository
public class BookDaoImpl implements BookDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public void add(Book book) {
        String sql = "insert into t_book set name=?,price=?";
        Object[] args = {book.getBookName(),book.getBookPrice()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println(update);
    }
    @Override
    public void update(Book book) {
        String sql = "update t_book set name=?,price=? where id=?";
        Object[] args = {book.getBookName(),book.getBookPrice(),book.getBookId()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println(update);
    }
    @Override
    public void delete(int id) {
        String sql = "delete from t_book where id=?";
        int update = jdbcTemplate.update(sql, id);
        System.out.println(update);
    }
    @Override
    public int queryCount() {
        String sql = "select count(*) from t_book";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        return count;
    }
    @Override
    public Book queryBookById(int id) {
        String sql = "select id bookId,name bookName,price bookPrice from t_book where id=?";
        Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
        return book;
    }
    @Override
    public List<Book> queryBooks() {
        String sql = "select id bookId,name bookName,price bookPrice from t_book";
        List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
        return bookList;
    }
    @Override
    public void batchAddBook(List<Object[]> books) {
        String sql = "insert into t_book set id=?,name=?,price=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, books);
        System.out.println(ints);
    }
    @Override
    public void batchUpdateBook(List<Object[]> books) {
        String sql = "update t_book set name=?,price=? where id=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, books);
        System.out.println(ints);
    }
    @Override
    public void batchDeleteBook(List<Object[]> args) {
        String sql = "delete from t_book where id=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, args);
        System.out.println(ints);
    }
}


@Service
public class BookService {
    @Autowired
    private BookDao bookDao = new BookDaoImpl();
    //添加图书
    public void add(Book book){
        bookDao.add(book);
    }
    //修改图书
    public void update(Book book){
        bookDao.update(book);
    }
    //删除图书
    public void delete(Integer id){
        bookDao.delete(id);
    }
    //查询数量
    public int queryCount(){
        return bookDao.queryCount();
    }
    //查询图书
    public Book queryBookById(Integer id){
        return bookDao.queryBookById(id);
    }
    //查询所有图书
    public List<Book> queryBooks(){
        return bookDao.queryBooks();
    }
    //批量添加图书
    public void batchAddBook(List<Object[]> books){
        bookDao.batchAddBook(books);
    }
    //批量修改图书
    public void batchUpdateBook(List<Object[]> books){
        bookDao.batchUpdateBook(books);
    }
    //批量删除图书
    public void batchDeleteBook(List<Object[]> args){
        bookDao.batchDeleteBook(args);
    }
}


五、事务管理


事务是数据库操作最基本单位,要么都成功,要么都失败。


典型场景:转账


事务四个特性ACID:原子性,一致性,隔离性,持久性。


Spring事务管理有两种方式:编程式事务管理 和 声明式事务管理,一般使用声明式事务管理,底层使用AOP原理。


声明式事务管理有两种方式:基于xml配置方式 和 基于注解方式,一般使用注解方式。

Spring事务管理提供了一个接口,叫做事务管理器,这个接口针对不同的框架提供不同的实现类。


对于使用JdbcTemplate进行数据库交互,则使用DataSourceTransactionManager实现类,如果整合Hibernate框架则使用HibernateTransactionManager实现类,具体情况具体使用。


(1)注解实现声明式事务管理:


<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/book" />
    <property name="username" value="root" />
    <property name="password" value="000000" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入数据库连接池-->
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>


在service类上面或者service类的方法上面添加事务注解@Transactional

如果把@Transactional添加在类上面,这个类里面所有方法都添加事务。 如果只是添加在方法上面,则只为这个方法添加事务。


@Service
@Transactional
public class UserService {}


声明式事务管理的参数配置:


propagation:事务传播行为,总共有7种,这一块讲的不是很清楚


isolation:事务隔离级别


有三个读问题:脏读,不可重复读,虚读(幻读)。


设置隔离级别,解决读问题:


脏读 不可重复读 虚读 READ UNCOMMITED(读未提交) 有 有 有 READ COMMITED(读已提交) 无 有 有 REPEATABLE READ(可重复读) 无 无 有 SERIALIZABLE(串行化) 无 无 无 timeout:超时时间


事务需要在一定时间内进行提交,超过时间后回滚。 默认值是-1,设置时间以秒为单位。 readOnly:是否只读 默认值为false,表示可以查询,也可以增删改。 设置为true,只能查询。 rollbackFor:回滚,设置出现哪些异常进行事务回滚。

noRollbackFor:不回滚,设置出现哪些异常不进行事务回滚。


@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public class AccountService {}


完全注解实现声明式事务管理:


配置类:


@Configuration  //配置类
@ComponentScan(basePackages = "com.oymn.spring5")  //开启组件扫描
@EnableTransactionManagement  //开启事务
public class Config {
    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/book");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("000000");
        return druidDataSource;
    }
    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    //创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}


@Service
public class AccountService {
    @Autowired
    private AccountDao accountDao;
    @Transactional
    public void accountMoney(){
        accountDao.add();
        //int i=1/0;   //用来模拟转账失败
        accountDao.reduce();
    }
}


(2)xml实现声明式事务管理:


<context:component-scan base-package="com.oymn"></context:component-scan>
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/book" />
    <property name="username" value="root" />
    <property name="password" value="000000" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入数据库连接池-->
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建事务管理器-->
<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" propagation="REQUIRED" />
    </tx:attributes>
</tx:advice>
<!--配置切入点和切面-->
<aop:config>
    <!--配置切入点-->
    <aop:pointcut id="pt" expression="execution(* com.oymn.spring5.Service.*.*(..))"/>
    <!--配置切面-->
    <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>


六、Spring5新特性


  1. 自带了日志封装 Spring5移除了Log4jConfigListener,官方建议使用Log4j2 Spring5整合Log4j2:


第一步:引入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>


  1. @Nullable注解 @Nullable注解可以用在方法上,属性上,参数上,表示方法返回值可以为空,属性可以为空,参数可以为空。


@Nullable     //表示方法返回值可以为空
public int getId();
@Nullable     //表示参数可以为空
public void setId(@Nullable int Id);
@Nullable     //表示属性可以为空
public int id;


  1. 支持函数式风格编程 这是因为java8新增了lamda表达式


@Test
    public void test() {
        //1 创建 GenericApplicationContext 对象
        GenericApplicationContext context = new GenericApplicationContext();
        //2 调用 context 的方法对象注册
        context.refresh();
        context.registerBean("user1",User.class,() -> new User());
        //3 获取在 spring 注册的对象
        // User user = (User)context.getBean("com.atguigu.spring5.test.User");
        User user = (User)context.getBean("user1");
        System.out.println(user);
    }


  1. 支持整合JUnit5 (1)整合JUnit4:


第一步:引入jar包


第二步:创建测试类,使用注解方式完成


@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架
    @ContextConfiguration("classpath:bean4.xml") //加载配置文件
    public class JUnitTest {
        @Autowired
        public User user;
        @Test
        public void test(){
            System.out.println(user);
        }
    }


bean4.xml:


<context:component-scan base-package="com.oymn"></context:component-scan>


通过使用@ContextConfiguration注解,测试方法中就不用每次都通过context来获取对象了,比较方便。


ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
BookService bookService = context.getBean("bookService",BookService.class);


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
6月前
|
Java Spring
【编程笔记】在 Spring 项目中使用 RestTemplate 发送网络请求
【编程笔记】在 Spring 项目中使用 RestTemplate 发送网络请求
124 0
|
1月前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
26 1
|
1月前
|
XML Java 数据库连接
【2020Spring编程实战笔记】Spring开发分享~(上)
【2020Spring编程实战笔记】Spring开发分享~
53 0
|
2月前
|
Java 数据库连接 API
【Java笔记+踩坑】Spring Data JPA
从常用注解、实体类和各层编写方法入手,详细介绍JPA框架在增删改查等方面的基本用法,以及填充用户名日期、分页查询等高级用法。
【Java笔记+踩坑】Spring Data JPA
|
2月前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
5月前
|
NoSQL 前端开发 Java
技术笔记:springboot分布式锁组件spring
技术笔记:springboot分布式锁组件spring
55 1
|
5月前
|
Java Linux 程序员
技术笔记:Spring生态研习【五】:Springboot中bean的条件注入
技术笔记:Spring生态研习【五】:Springboot中bean的条件注入
|
5月前
|
XML Java 数据安全/隐私保护
技术笔记:Spring中的通知(Advice)和顾问(Advisor)
技术笔记:Spring中的通知(Advice)和顾问(Advisor)
73 0
|
6月前
|
前端开发 Java 数据格式
【Spring系列笔记】定义Bean的方式
在Spring Boot应用程序中,定义Bean是非常常见的操作,它是构建应用程序的基础。Spring Boot提供了多种方式来定义Bean,每种方式都有其适用的场景和优势。
111 2
|
6月前
|
Java Spring 容器
【Spring系列笔记】IOC与DI
IoC 和 DI 是面向对象编程中的两个相关概念,它们主要用于解决程序中的依赖管理和解耦问题。 控制反转是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入和依赖查找。
97 2