二、Java框架之Spring注解开发

news/2024/11/16 13:31:37/

文章目录

  • 1. IOC/DI注解开发
    • 1.1 Component注解
      • @Component
      • @Controller @Service @Repository
    • 1.2 纯注解开发模式
    • 1.3 注解开发bean管理
      • @Scope
      • @PostConstruct @PreDestroy
    • 1.4 注解开发依赖注入
      • @Autowired @Qualifier
      • @Value
      • @PropertySource
    • 1.5 第三方bean管理
      • @Bean
      • @import(多个Config类)
      • 引用类型的注入
      • 总结
    • 1.6 XML配置和注解配置对比
  • 2. Spring整合MyBatis
    • 2.1 mybatis写法回顾
    • 2.2 整合:导入依赖:pom.xml
    • 2.3 整合:环境准备
    • 2.4 整合:Spring核心配置文件
      • SpringConfig.java
      • JdbcConfig.java
      • MybatisConfig.java
    • 2.5 运行和说明
  • 3. Spring整合JUnit
  • 4. AOP
    • 4.1 AOP核心概念
    • 4.2 AOP入门案例
      • @EnableAspectJAutoProxy @Aspect @Pointcut @Before
    • 4.3 AOP原理
      • AOP工作流程
      • AOP核心概念 - 代理
    • 4.4 AOP切入点表达式
    • 4.5 AOP通知类型
    • 4.6 案例:业务层接口执行效率
    • 4.7 AOP通知获取数据
  • 5. AOP事务管理
    • 5.1 Spring事务简介
    • 5.2 Spring事务案例
      • 无事务管理情况
      • 开启事务处理
    • 5.3 Spring事务角色
    • 5.4 Spring事务属性
      • 事务配置
      • 案例:转账业务追加案例
      • 事务传播行为

从Spring2开始引入注解,Spring3已经可以纯注解开发,以避免使用复杂的配置文件

1. IOC/DI注解开发

1.1 Component注解

@Component

  • 在对应类上添加Component注解

    在这里插入图片描述

  • 在applicationContext.xml指定要扫描的路径
    在这里插入图片描述

    注意:这里首先创建了context命名空间,然后使用了component-scan base-package,之后就可以正常获取bean了

    • 扫描的范围是 base-package 指定的范围
  • 测试BookService

    //BookServiceImpl.java
    @Component
    //可以不添加名称,之后按类型获取
    
    //applicationContext.xml
    <context:component-scan base-package="org.example"/>
    
    //App.java
    BookService bookService = ctx.getBean(BookService.class);
    bookService.save();
    

@Controller @Service @Repository

这三个注解是Component的衍生注解,作用和Component相同,只是为了区分某个类是属于表现层业务层还是数据层的类

在这里插入图片描述

  • Controller注解:表现层,例如BooServlet.java
  • Service注解:业务层,例如BookServiceImpl.java
  • Repository注解:数据层,例如BookDaoImpl.java(代表mybatis里面的mapper部分)

1.2 纯注解开发模式

不再写applicationContext.xml配置文件,而是用Config类替代

  • 创建Config类
    在这里插入图片描述

    @Configuration//表示这是个配置类,相当于applicationContext.xml默认部分,如命令空间xmlns那一块内容
    @ComponentScan("org.example.dao")//相当于设置了<bean>标签
    public class SpringConfig {
    }
    

    之前的applicationContext.xml已经可以删除了

    • @Configuration:设置该类为spring配置类

    • @ComponentScan:设置spring配置类扫描路径,此注解只能添加一次,多个数据用{}格式,如

      @ComponentScan({"org.example.dao", "org.example.service"})
      
  • BookDaoImpl.java

    package org.example.dao.impl;@Repository("bookDao")
    public class BookDaoImpl implements BookDao {public void save() {System.out.println("book dao save ...");}
    }
  • 使用SpringConfig:AnnotationConfigApplicationContext

    public class APP {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao = (BookDao) ctx.getBean("bookDao");System.out.println(bookDao);BookService bookService = ctx.getBean(BookService.class);System.out.println(bookService);}
    }
    

1.3 注解开发bean管理

@Scope

设置是否为单例模式

在这里插入图片描述

@PostConstruct @PreDestroy

管理生命周期 init() 和 destroy()

在这里插入图片描述

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao);
ctx.close();//关闭容器,从而可以看到destroy()的信息

1.4 注解开发依赖注入

@Autowired @Qualifier

在这里插入图片描述

如果只有一个实现类implements BookDao时,仅需@Autowired即可自动注入
如果只有多个实现类implements BookDao时,还需@Qualifier(“name”)指定哪一个实现类

使用@Autowired可以省略setter方法

@Value

在这里插入图片描述

name变量被注入了值 “example”
这样单纯使用@Value是没有意义的,注解主要是为了加载properties文件,使得变量值可更改

@PropertySource

读取Properties配置文件

  • 新建jdbc.properties

    name=example
    
  • 配置Config类

    package org.example.config;@Configuration
    @ComponentScan({"org.example.dao", "org.example.service"})
    @PropertySource("classpath:jdbc.properties")
    public class SpringConfig {
    }
    
  • 注入

    package org.example.dao.impl;@Repository("bookDao")
    public class BookDaoImpl implements BookDao {@Value("${name}")private String name;public void save() {System.out.println("book dao save ..."+name);}
    }
    

注意事项:(1)多个properties配置文件同样使用{}格式;(2)不支持通配符

1.5 第三方bean管理

@Bean

  • 导入依赖

    <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version>
    </dependency>
    
  • 配置Config文件

    @Configuration
    public class SpringConfig {//1. 定义一个方法获得要管理的对象//2. 添加@Bean,表示当前方法的返回值是一个bean@Beanpublic DataSource dataSource(){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql://localhost:3306/spring_db");ds.setUsername("root");ds.setPassword("root");return ds;}
    }
    
  • 获取Bean并运行

    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    DataSource dataSource = ctx.getBean(DataSource.class);
    System.out.println(dataSource);
    

@import(多个Config类)

像是上面的dataSource()这类的通常会专门创建一个Config类,如JdbcConfig,现在需要使其生效
在这里插入图片描述

方法一(不推荐)

  • JdbcConfig.java

    @Configuration
    public class JdbcConfig {@Beanpublic DataSource dataSource(){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql://localhost:3306/spring_db");ds.setUsername("root");ds.setPassword("root");return ds;}
    }
    
  • 还需要配置SpringConfig.java

    @Configuration
    @ComponentScan("org.example.config")
    public class SpringConfig {
    }
    

方法二(推荐)

  • JdbcConfig.java

    public class JdbcConfig {//注意,没再使用@Configuration@Beanpublic DataSource dataSource(){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql://localhost:3306/spring_db");ds.setUsername("root");ds.setPassword("root");return ds;}
    }
    
  • 配置SpringConfig.java

    @Configuration
    @Import(JdbcConfig.class)
    public class SpringConfig {
    }
    

练习:使用@Value和properties文件修改上述代码

引用类型的注入

  • BookDaoImpl.java

    @Repository
    public class BookDaoImpl implements BookDao {public void save() {System.out.println("book dao save ...");}
    }
    
  • SpringConfig.java

    @Configuration
    @ComponentScan("org.example.dao")//关联到BookDaoImpl
    @Import(JdbcConfig.class)
    public class SpringConfig {
    }
    
  • JdbcConfig.java

    @PropertySource("classpath:jdbc.properties")
    public class JdbcConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Beanpublic DataSource dataSource(BookDao bookDao){System.out.println(bookDao);DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(driver);ds.setUrl(url);ds.setUsername(username);ds.setPassword(password);return ds;}
    }
    
  • 自动装配
    上面仅提供了一个形参bookDao,即可自动注入
    这是因为@Bean使其认为形参应当被自动提供,于是将自动寻找相应的类,并注入到形参中

总结

  • 1.第三方Bean管理
    • @Bean
  • 2.第三方依赖注入
    • 引用类型:方法形参
    • 简单类型:成员变量

1.6 XML配置和注解配置对比

在这里插入图片描述

2. Spring整合MyBatis

2.1 mybatis写法回顾

在这里插入图片描述

  1. 创建javaweb项目,在pom.xml添加<packaging>war</packaging>

  2. 配置pom.xml依赖和插件

    <dependencies><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.46</version></dependency><!-- mybatis --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.5</version></dependency>
    </dependencies><build><plugins><!--Tomcat插件,非必要 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version></plugin></plugins>
    </build>
    
  3. 编写mybatis-config.xml配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration><properties resource="jdbc.properties"/><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><!--数据库连接信息--><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><package name="org.example.mapper"/></mappers>
    </configuration>
    

    jdbc.properties

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql:///spring_db?useSSL=false&amp;useServerPrepStmts=true
    jdbc.username=root
    jdbc.password=123456
    
  4. 创建AcccountMapper.xml和AccontMapper接口
    AccountMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.example.mapper.AccountMapper"></mapper>
    

    AccontMapper接口

    package org.example.mapper;public interface AccountMapper {@Insert("insert into tbl_account(name,money)values(#{name},#{money})")void save(Account account);@Delete("delete from tbl_account where id = #{id} ")void delete(Integer id);@Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")void update(Account account);@Select("select * from tbl_account")List<Account> findAll();@Select("select * from tbl_account where id = #{id} ")Account findById(Integer id);
    }
    

    在这一部分定义sql语句

  5. 编写service方法负责业务逻辑层,主要是调用数据库
    准备工具类:SqlSessionFactoryUtils

    package org.example.util;public class SqlSessionFactoryUtils {private static SqlSessionFactory sqlSessionFactory;static{try {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);} catch (IOException e) {e.printStackTrace();}}public static SqlSessionFactory getSqlSessionFactory(){return sqlSessionFactory;}
    }
    

    编写AccountService接口

    public interface AccountService {List<Account> findAll();
    }
    

    AccountService.java

    package org.example.service.impl;public class AccountServiceImpl implements AccountService {private SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();@Overridepublic List<Account> findAll() {SqlSession session = factory.openSession();AccountMapper mapper = session.getMapper(AccountMapper.class);List<Account> accounts = mapper.findAll();session.close();return accounts;}
    }
    
  6. 接下来应该是在servlet类里面调用service方法,这里写在main函数里面

    package org.example;public class Main {public static void main(String[] args) {AccountService service = new AccountServiceImpl();List<Account> accounts = service.findAll();System.out.println(accounts);}
    }
    

即可成功获取到数据库数据
当Spring需要整合mybatis时,真正需要交给Spring管理的是SqlSessionFactory

2.2 整合:导入依赖:pom.xml

<dependencies><!-- spring-context --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.10.RELEASE</version></dependency><!-- druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><!-- mybatis --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.5</version></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.46</version></dependency><!-- spring-jdbc --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.10.RELEASE</version></dependency><!-- mybatis-spring --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.0</version></dependency>
</dependencies>

2.3 整合:环境准备

步骤1:准备数据库表

create database spring_db character set utf8;
use spring_db;
create table tbl_account(id int primary key auto_increment,name varchar(35),money double
);
insert into tbl_account values (null, 'zhangsan', 1999.10);
insert into tbl_account values (null, '张三', 32.43);

步骤2:创建基础文件

在这里插入图片描述

Account.java

package org.example.domain;public class Account{private Integer id;private String name;private Double money;
}
//省略getter, setter, toString

AccountDao接口

这里的AccountDao就是AccountMapper的作用,需要加上注解

package org.example.dao;@Repository("accountDao")
public interface AccountDao {@Insert("insert into tbl_account(name,money)values(#{name},#{money})")void save(Account account);@Delete("delete from tbl_account where id = #{id} ")void delete(Integer id);@Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")void update(Account account);@Select("select * from tbl_account")List<Account> findAll();@Select("select * from tbl_account where id = #{id} ")Account findById(Integer id);
}

Service接口和实现类

接口是没有变化的

package org.example.service;public interface AccountService {void save(Account account);void delete(Integer id);void update(Account account);List<Account> findAll();Account findById(Integer id);
}

实现类变化很大,和之前相比,spring会接管SqlSessionFactory对象的创建,因此这次不需要创建了
重点:@Service和自动注入

package org.example.service.impl;@Service
public class AccountServiceImpl implements AccountService {@Autowired//自动注入@Qualifier("accountDao")private AccountDao accountDao;public void save(Account account) {accountDao.save(account);}public void update(Account account){accountDao.update(account);}public void delete(Integer id) {accountDao.delete(id);}public Account findById(Integer id) {return accountDao.findById(id);}public List<Account> findAll() {return accountDao.findAll();}
}

jdbc.properties

resources目录下添加,用于配置数据库连接四要素

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=123456

useSSL:关闭MySQL的SSL连接

2.4 整合:Spring核心配置文件

在没有整合之前,mybatis的service类里面会创建SqlSessionFactory对象,来与数据库互通
在整合后,可以看到新的service类里面不再具备这样的功能
spring核心配置文件就是用来设置配置信息的,用以替代mybatis-config.xml等配置文件 ,并管理bean之间的依赖关系

SpringConfig.java

主配置类,推荐在这个配置类里面import其他配置类

package org.example.config;@Configuration//说明这是一个配置类
@ComponentScan("org.example")//定义扫描路径
@PropertySource("classpath:jdbc.properties")//引入连接信息资源文件
@Import({JdbcConfig.class, MybatisConfig.class})//要么这里导入,要么在 JdbcConfig 前面加 @Configuration
public class SpringConfig {
}

JdbcConfig.java

package org.example.config;//定义数据源	
//本来是需要引入jdbc.properties的,但这里选择将所有文件都放在SpringConfig里面引入
public class JdbcConfig {@Value("${jdbc.driver}")//自动注入private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Beanpublic DataSource dataSource(BookDao bookDao){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(driver);ds.setUrl(url);ds.setUsername(username);ds.setPassword(password);return ds;}
}

MybatisConfig.java

SqlSessionFactoryBean来源于org.mybatis.spring,可以直接获取SqlSessionFactory

package org.example.config;import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;import javax.sql.DataSource;public class MybatisConfig {//sqlSessionFactoryBean完成了mybatis-config里面的<environment>部分@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){//dataSource也是一个Bean,所以这里能够自动注入SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();ssfb.setTypeAliasesPackage("org.example.domain");//取别名,domain是实体类包,相当于之前的pojo包ssfb.setDataSource(dataSource);//设置数据源,即连接相关信息return ssfb;}//mapperScannerConfigurer完成了mybatis-config里面的<mappers>部分@Beanpublic MapperScannerConfigurer mapperScannerConfigurer(){MapperScannerConfigurer msc = new MapperScannerConfigurer();msc.setBasePackage("org.example.dao");//这里的dao包实际上就是之前学mybatis里面的mapper包return msc;}
}

2.5 运行和说明

App.java

package org.example;public class App {public static void main(String[] args){ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);AccountService accountService = ctx.getBean(AccountService.class);//@Service标注会自动生成beanAccount account = accountService.findById(1);//即使后面AccountServiceImpl修改,也不影响这里的代码System.out.println(account);}
}

说明

  • 运行流程

    - 程序启动时候检测使用了@Configuration注解的配置类SpringConfig
    - SpringConfig中引入了MybatisConfig和JdbcConfig,相当于这三个文件都成为一个配置文件
    - MybatisConfig通过JdbcConfig获取到了dataSource,里面带有配置数据库连接的信息,从而成功创建 SqlSessionFactory
    - 由于AccountServiceImpl.java上使用了注解@Service,且配置类SpringConfig定义了扫描路径"org.example",于是它将被纳入bean管理
    - 执行ctx.getBean(AccountService.class),这里实际上是以接口类去接实现类,类似于Father father = new Son();
    - 调用实现类的findById方法
    
  • 关于Spring注入的是接口还是实现类?
    参考:https://blog.csdn.net/m0_51697147/article/details/126802648

    • 在配置文件模式中,配置bean

      <bean id="bookService" class="org.example.service.BookServiceImpl"><property name="bookDao" ref="bookDao"/>
      </bean>
      

      获取bean

      BookService bookService = ctx.getBean(BookService.class);
      
    • 在注解开发模式中,配置bean

      @Service
      public class AccountServiceImpl implements AccountService {@Autowired@Qualifier("accountDao")private AccountDao accountDao;...
      }
      

      获取bean

      AccountService accountService = ctx.getBean(AccountService.class);
      

    从spring容器中获取一个类,如果这个类实现了一个接口并且该类存在一个AOP的切入点方法,那么通过getBean()获取到的bean类型只能是这个类的接口类型,不能是具体实现

    getBean()必须面向接口,这是因为底层实现用了代理,并由Proxy的内部实现决定

    优点:如果之后实现类发生改变,例如修改为AccountServiceImpl2.java,那么App.java里面的内容不必修改

    思考:如果有多个实现类继承了AccountService,这也写将会报错,那么如何处理?

3. Spring整合JUnit

1.导入依赖

<!-- junit -->
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope>
</dependency>
<!-- spring test -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.2.10.RELEASE</version>
</dependency>

2.编写测试类

package org.example.service;@RunWith(SpringJUnit4ClassRunner.class)//设定类运行器
@ContextConfiguration(classes = SpringConfig.class)//加载配置类
//@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载配置文件
public class AccountServiceTest {//支持自动装配注入bean@Autowiredprivate  AccountService accountService;@Testpublic void testFindById(){System.out.println(accountService.findById(2));}@Testpublic void testFindAll(){System.out.println(accountService.findAll());}
}

要测试哪个方法,就在哪个方法那里点击执行

  • 单元测试,如果测试的是注解配置类,则使用@ContextConfiguration(classes = 配置类.class)
  • 单元测试,如果测试的是配置文件,则使用@ContextConfiguration(locations={配置文件名,...})
  • Junit运行后是基于Spring环境运行的,所以Spring提供了一个专用的类运行器,这个务必要设置,这个类运行器就在Spring的测试专用包中提供的,导入的坐标就是这个东西SpringJUnit4ClassRunner
  • 上面两个配置都是固定格式,当需要测试哪个bean时,使用自动装配加载对应的对象

知识点1:@RunWith

名称@RunWith
类型测试类注解
位置测试类定义上方
作用设置JUnit运行器
属性value(默认):运行所使用的运行期

知识点2:@ContextConfiguration

名称@ContextConfiguration
类型测试类注解
位置测试类定义上方
作用设置JUnit加载的Spring核心配置
属性classes:核心配置类,可以使用数组的格式设定加载多个配置类
locations:配置文件,可以使用数组的格式设定加载多个配置文件名称

4. AOP

AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
OOP(Object Oriented Programming)面向对象编程

Spring有两个核心的概念,一个是IOC/DI,一个是AOP
作用:AOP是在不改原有代码的前提下对其进行增强
Spring理念:无入侵时/无侵入式

4.1 AOP核心概念

package org.example.dao.impl;import org.example.dao.BookDao;
import org.springframework.stereotype.Repository;@Repository
public class BookDaoImpl implements BookDao {public void save() {//记录程序当前执行执行(开始时间)Long startTime = System.currentTimeMillis();//业务执行万次for (int i = 0;i<10000;i++) {System.out.println("book Dao");}//记录程序当前执行时间(结束时间)Long endTime = System.currentTimeMillis();//计算时间差Long totalTime = endTime-startTime;//输出信息System.out.println("执行万次消耗时间:" + totalTime + "ms");}public void update(){System.out.println("book dao update ...");}public void delete(){System.out.println("book dao delete ...");}public void select(){System.out.println("book dao select ...");}
}

需求:希望对update、delete函数执行和save一样的流程,即执行10000次,然后打印时间差

在这里插入图片描述

AOP中的核心概念

  • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等

    • 在SpringAOP中,理解为方法的执行
    • AOP将每一个方法调用,即连接点作为编程的入口,针对方法调用进行编程
  • 切入点(Pointcut):匹配连接点的式子

    • 指需要被增强的方法
    • 切入点是连接点,但连接点不一定是切入点
  • 通知(Advice):在切入点处执行的操作,也就是共性功能

    • 如上面的计算万次执行消耗时间作为共性功能,被抽取到一个方法中,这个方法就是通知
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类

  • 切面(Aspect):描述通知与切入点的对应关系。

    • 通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,通知与切入点的对应关系叫切面

4.2 AOP入门案例

需求:在方法执行前输出当前系统时间。

开发模式:XML 和 注解

步骤:

  1. 导入坐标(pom.xml)

    <!-- spring-context里面包含了aop -->
    <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.10.RELEASE</version>
    </dependency>
    <!-- aspectjweaver -->
    <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
    </dependency>
    
  2. 制作连接点(原始操作,Dao接口与实现类)

    package org.example.dao;public interface BookDao {public void save();public void update();
    }
    
    package org.example.dao.impl;@Repository
    public class BookDaoImpl implements BookDao {public void save(){System.out.println(System.currentTimeMillis());System.out.println("book dao save ...");}public void update(){System.out.println("book dao update ...");}
    }
    
  3. 制作共性功能(通知类与通知)

    新建包aop,新建MyAdvice通知类,printTime即为通知方法

  4. 定义切入点

    切入点即 pt() ,需要注解@Pointcut注明哪些方法需要被增强

  5. 绑定切入点与通知关系(切面)

    @Before说明切入点与通知的关系

  6. 配置Spring环境

    package org.example.aop;//6. 配置Spring环境
    @Component//需要将其交给Spring管理
    @Aspect//告诉Spring当作AOP处理,而非Bean
    public class MyAdvice {//4. 定义切入点@Pointcut("execution(void org.example.dao.BookDao.update())")private void pt(){}//5. 绑定切入点与通知关系(切面)@Before("pt()")//在pt()方法前执行//3. 制作共性功能(通知类与通知)public void printTime(){System.out.println(System.currentTimeMillis());}
    }
    
    package org.example.config;@Configuration
    @ComponentScan("org.example")
    @EnableAspectJAutoProxy//开启Spring对AOP注解驱动支持
    public class SpringConfig {
    }
    
  7. 运行

    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    BookDao bookDao = ctx.getBean(BookDao.class);
    bookDao.update();
    

@EnableAspectJAutoProxy @Aspect @Pointcut @Before

名称@EnableAspectJAutoProxy
类型配置类注解
位置配置类定义上方
作用开启注解格式AOP功能
名称@Aspect
类型类注解
位置切面类定义上方
作用设置当前类为AOP切面类
名称@Pointcut
类型方法注解
位置切入点方法定义上方
作用设置切入点方法
属性value(默认):切入点表达式
名称@Before
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行

4.3 AOP原理

AOP工作流程

由于AOP是基于Spring容器管理的bean做的增强,所以整个工作过程需要从Spring加载bean说起

工作流程

  1. 流程1:Spring容器启动

    容器启动就需要去加载bean,带有@Component,@Service ,@Controller 的类都是spring 要创建的bean对象

    • 需要被增强的类BookDaoImpl,通知类MyAdvice
    • 注意此时bean对象还没有创建成功
  2. 流程2:读取所有切面配置中的切入点

    @Component
    @Aspect
    public class MyAdvice {@Pointcut("execution(void org.example.dao.BookDao.save())")private void ptx(){}@Pointcut("execution(void org.example.dao.BookDao.update())")private void pt(){}@Before("pt()")public void printTime(){System.out.println(System.currentTimeMillis());}
    }
    

    有两个切入点,其中切入点ptx()并没有被使用,所以不会被读取

  3. 流程3:初始化bean

    在容器启动的时候,bean对象还没有被创建成功
    在创建bean对象时,需要判定bean对应的类中的方法是否匹配到任意切入点,以BookDao为例

    • 匹配失败,创建原始对象,即BookDao本身的对象
      • 匹配失败,即该类中没有一个方法能匹配上切入点,说明不需要增强,直接调用原始对象的方法即可
    • 匹配成功,创建原始对象(目标对象)的代理对象
      • 匹配成功说明需要对其进行增强
      • 对哪个类做增强,这个类对应的对象就叫做目标对象
      • 因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其创建一个代理对象
      • 最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强
  4. 流程4:获取bean并执行方法

    • 获取的bean是原始对象时,调用方法并执行,完成操作
    • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

验证代理

System.out.println(bookDao);
System.out.println(bookDao.getClass());

在这里插入图片描述

打印bookDao时,由于代理里面重写了toString,所以看到的是BookDaoImpl
打印Class就可以看到,最终生成的是目标对象的代理对象

AOP核心概念 - 代理

  • 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
  • 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

SpringAOP是在不改变原有设计(代码)的前提下对其进行增强的,它的底层采用的是代理模式实现的,所以要对原始对象进行增强,就需要对原始对象创建代理对象,在代理对象中的方法把通知[如:MyAdvice中的method方法]内容加进去,就实现了增强,这就是我们所说的代理(Proxy)。

SpringAOP的本质或者可以说底层实现是通过代理模式

4.4 AOP切入点表达式

切入点:要进行增强的方法

切入点表达式:要进行增强的方法的描述方式

  • 接口描述

    execution(void org.example.dao.BookDao.update())
    
  • 实现类描述

    execution(void org.example.dao.impl.BookDaoImpl.update())
    

因为调用接口方法的时候最终运行的还是其实现类的方法,所以上面两种描述方式都是可以的

切入点表达式标准格式

动作关键字(访问修饰符  返回值  包名.类/接口名.方法名(参数) 异常名)
execution(public User org.example.service.UserService.findById(int))

切入点通配符

  • *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

    execution(public * org.example.*.UserService.find*(*))
    

    匹配org.example包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

  • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

    execution(public User org..UserService.findById(..))
    

    匹配org包下的任意包中的UserService类或接口中所有名称为findById的方法

  • +:专用于匹配子类类型

    execution(* *..*Service+.*(..))
    

    很少使用。*Service+,表示所有以Service结尾的接口的子类。

切入点表达式练习

//匹配接口,能匹配到
execution(void org.example.dao.BookDao.update())
//匹配实现类,能匹配到
execution(void org.example.dao.impl.BookDaoImpl.update())
//返回值任意,能匹配到
execution(* org.example.dao.impl.BookDaoImpl.update())
//返回值任意,但是update方法必须要有一个参数,无法匹配,要想匹配需要在update接口和实现类添加参数
execution(* org.example.dao.impl.BookDaoImpl.update(*))
//返回值为void,org包下的任意包三层包下的任意类的update方法,匹配到的是实现类,能匹配
execution(void org.*.*.*.*.update())
//返回值为void,org包下的任意两层包下的任意类的update方法,匹配到的是接口,能匹配
execution(void org.*.*.*.update())
//返回值为void,方法名是update的任意包下的任意类,能匹配
execution(void *..update())
//匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
execution(* *..*(..))
//匹配项目中任意包任意类下只要以u开头的方法,update方法能满足,能匹配
execution(* *..u*(..))
//匹配项目中任意包任意类下只要以e结尾的方法,update和save方法能满足,能匹配
execution(* *..*e(..))
//返回值为void,org包下的任意包任意类任意方法,能匹配,*代表的是方法(这个代表方法的*不能省略)
execution(void org..*())
//将项目中所有业务层方法的以find开头的方法匹配
execution(* org.example.*.*Service.find*(..))
//将项目中所有业务层方法的以save开头的方法匹配
execution(* org.example.*.*Service.save*(..))

书写技巧

  • 所有代码按照标准规范开发
  • 描述切入点通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
  • 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
  • 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配
  • 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名
  • 方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成selectAll
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常不使用异常作为匹配规则

4.5 AOP通知类型

5种通知类型

  • 前置通知

    @Before("pt()")
    
  • 后置通知

    @After("pt()")
    
  • 环绕通知(重点)

    package org.example.aop;@Component
    @Aspect
    public class MyAdvice {@Pointcut("execution(void org.example.dao.BookDao.update())")private void pt(){}@Around("pt()")public void aroundSelect(ProceedingJoinPoint pjp) throws Throwable {//前置System.out.println("before advice");//原始操作pjp.proceed();//后置System.out.println("after advice");}
    }
    

    有返回值的情况

    package org.example.aop;@Component
    @Aspect
    public class MyAdvice {@Pointcut("execution(int org.example.dao.BookDao.select())")private void pt(){}@Around("pt()")public Object aroundUpdate(ProceedingJoinPoint pjp) throws Throwable {//前置System.out.println("before advice");//原始操作Object ret = pjp.proceed();//后置System.out.println("after advice");return ret;}
    }
    
  • 返回后通知(了解)

    @AfterReturning("pt()")
    

    返回后通知是需要在原始方法select正常执行后才会被执行,如果过程中出现了异常,那么返回后通知是不会被执行
    后置通知是不管原始方法有没有抛出异常都会被执行

  • 抛出异常后通知(了解)

    @AfterThrowing("pt()")
    

    如果有异常才会执行

注意事项

  • 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用
  • 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为Object类型
  • 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常

4.6 案例:业务层接口执行效率

需求:任意业务层接口执行均可显示其执行效率(执行时长)
环境准备:使用前面整合MyBatis和Junit之后的项目

  1. 添加pom.xml依赖

    <!-- spring-context -->
    <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.10.RELEASE</version>
    </dependency>
    <!-- aspectjweaver -->
    <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
    </dependency>
    
  2. 配置SpringConfig环境

    package org.example.config;@Configuration
    @ComponentScan("org.example")
    @PropertySource("classpath:jdbc.properties")
    @Import({JdbcConfig.class, MybatisConfig.class})
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
    
  3. 创建通知类 org.example.aop.ProjectAdvice

  4. 编写通知方法

    package org.example.aop;@Component
    @Aspect
    public class ProjectAdvice {//1. 切入点:匹配业务层的所有方法@Pointcut("execution(* org.example.service.*Service.*(..))")private void servicePt(){}//2. 环绕方法@Around("ProjectAdvice.servicePt()")public void runSpeed(ProceedingJoinPoint pjp) throws Throwable{//ProceedingJoinPoint:连接点,携带原始方法信息Signature signature = pjp.getSignature();String className = signature.getDeclaringTypeName();String methodName = signature.getName();//前置:获取开始时间long start  = System.currentTimeMillis();for(int i=0; i<10000; ++i){//调用原始方法Object ret = pjp.proceed();}//后置:获取结束时间long end = System.currentTimeMillis();System.out.println("万次执行:"+className+"."+methodName+" 时间为:"+(end-start)+"ms");}
    }
    
  5. 测试类

    package org.example.service;@RunWith(SpringJUnit4ClassRunner.class)//设定类运行器
    @ContextConfiguration(classes = SpringConfig.class)
    public class AccountServiceTest {@Autowiredprivate  AccountService accountService;@Testpublic void testFindById(){accountService.findById(2);}@Testpublic void testFindAll(){accountService.findAll();}
    }
    

4.7 AOP通知获取数据

  • 获取切入点方法的参数,所有的通知类型都可以获取参数
    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    • ProceedingJoinPoint:适用于环绕通知
  • 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
    • 抛出异常后通知
    • 环绕通知

获取参数

package org.example.aop;@Component
@Aspect
public class MyAdvice {@Pointcut("execution(* org.example.dao.BookDao.findName(..))")private void pt(){}@Before("pt()")public void before(JoinPoint jp){Object[] args = jp.getArgs();}@After("pt()")public void after(JoinPoint jp){Object[] args = jp.getArgs();}@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable{Object[] args = pjp.getArgs();args[0] = 666;//可以中途修改参数Object ret = pjp.proceed(args);return ret;}
}

环绕方法可以修改传递过来的参数,有时可以用作对参数清洗

返回值

@AfterReturning(value = "pt()", returning = "ret")
public void afterReturning(JoinPoint jp, Object ret){//注意如果有JoinPoint参数,它必须得在第一位System.out.println("afterReturning advice ..."+ret);//ret即为返回值
}

获取异常

@AfterThrowing(value = "pt()", throwing = "t")
public void afterThrowing(Throwable t){System.out.println("afterThrowing advice .."+t);
}

5. AOP事务管理

5.1 Spring事务简介

  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败

Spring为了管理事务,提供了一个平台事务管理器PlatformTransactionManager
在这里插入图片描述

commit是用来提交事务,rollback是用来回滚事务

PlatformTransactionManager只是一个接口,Spring还为其提供了一个具体的实现

只需要给它一个DataSource对象,它就可以帮你去在业务层管理事务。其内部采用的是JDBC的事务
所以如果你持久层采用的是JDBC相关的技术,就可以采用这个事务管理器来管理事务。而Mybatis内部采用的就是JDBC的事务,所以后期Spring整合Mybatis就采用的这个DataSourceTransactionManager事务管理器。

5.2 Spring事务案例

无事务管理情况

需求: 实现任意两个账户间转账操作,A账户减钱和B账户加钱必须是同成功或同失败
准备工作:第2节中整合MyBatis中的spring-mybatis项目

步骤1:准备数据库表

含有 id name money 三个属性的数据库表

步骤2:创建项目导入jar包

步骤3:根据表创建模型类

即Account类

步骤4:创建Dao接口

在AccountDao.java中加入

@Update("update tbl_account set money = money + #{money} where name = #{name}")
void inMoney(@Param("name") String name, @Param("money") Double money);@Update("update tbl_account set money = money - #{money} where name = #{name}")
void outMoney(@Param("name") String name, @Param("money") Double money);

步骤5:编写Service接口和实现类

package org.example.service;public interface AccountService {/*** 转账* @param out:转出账户* @param in:转入账户* @param money:转账金额*/public void transfer(String out, String in, Double money);
}
package org.example.service.impl;@Service
public class AccountServiceImpl implements AccountService {//自动注入accountDao@Autowired@Qualifier("accountDao")private AccountDao accountDao;@Overridepublic void transfer(String out, String in, Double money) {accountDao.outMoney(out, money);accountDao.inMoney(in, money);}
}

步骤6:编写配置类

SpringConfig,JdbcConfig,MybatisConfig,jdbc.properties

步骤7:编写测试类

package org.example.service;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {@Autowiredprivate  AccountService accountService;@Testpublic void testTransfer() throws IOException{accountService.transfer("zhangsan", "lisi", 100D);}
}

问题

当增加和修改两个操作中间出现异常时,会出现一个账户减少了,而另一个账户却没增加的错误!,如:

public void transfer(String out, String in, Double money) {accountDao.outMoney(out, money);int i = 1/0;accountDao.inMoney(in, money);
}

开启事务处理

步骤1:添加@Transactional注解

可以写在接口类上、接口方法上、实现类上和实现类方法上

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务
  • 写在接口方法上,该接口的所有实现类的该方法都会有事务
  • 写在实现类上,该类中的所有方法都会有事务
  • 写在实现类方法上,该方法上有事务
  • 常写在方法前
package org.example.service;public interface AccountService {@Transactionalpublic void transfer(String out, String in, Double money);
}

步骤2:在JdbcConfig类中配置事务管理器

package org.example.config;public class JdbcConfig {@Value("${jdbc.driver}")//自动注入private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Beanpublic DataSource dataSource(){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(driver);ds.setUrl(url);ds.setUsername(username);ds.setPassword(password);return ds;}@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource){//自动注入dataSourceDataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;}
}

事务管理器要根据使用技术进行选择,Mybatis框架使用的是JDBC事务,可以直接使用DataSourceTransactionManager

步骤3:在SpringConfig中开启事务注解

package org.example.config;import org.springframework.context.annotation.*;
import org.springframework.transaction.annotation.EnableTransactionManagement;@Configuration
@ComponentScan("org.example")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

至此即可实现transfer函数的同成功或同失败

5.3 Spring事务角色

  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法,如transfer()

  • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法,如outMoney()inMoney

  1. 未开启Spring事务之前

在这里插入图片描述

  • AccountDao的outMoney因为是修改操作,会开启一个事务T1
  • AccountDao的inMoney因为是修改操作,会开启一个事务T2
  • AccountService的transfer没有事务,
    • 运行过程中如果没有抛出异常,则T1和T2都正常提交,数据正确
    • 如果在两个方法中间抛出异常,T1因为执行成功提交事务,T2因为抛异常不会被执行
    • 就会导致数据出现错误
  1. 开启Spring的事务管理后

在这里插入图片描述

  • transfer上添加了@Transactional注解,在该方法上就会有一个事务T
  • AccountDao的outMoney方法的事务T1加入到transfer的事务T中
  • AccountDao的inMoney方法的事务T2加入到transfer的事务T中
  • 这样就保证他们在同一个事务中,当业务层中出现异常,整个事务就会回滚,保证数据的准确性。

目前的事务管理是基于DataSourceTransactionManagerSqlSessionFactoryBean使用的是同一个数据源

5.4 Spring事务属性

事务配置

在这里插入图片描述

@Transactional(readOnly = true, timeout = -1)
  • rollbackFor(重点)

    当transfer()的代码如下时,先前的事务管理失效,仍然导致一方改变了,而另一方未改变

    @Override
    public void transfer(String out, String in, Double money) throws IOException {accountDao.outMoney(out, money);if(true) throw new IOException();accountDao.inMoney(in, money);
    }
    

    原因:Spring的事务只会对Error异常RuntimeException异常及其子类进行事务回滚,其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚

    修改:设置rollbackFor

    @Transactional(rollbackFor = {IOException.class})
    public void transfer(String out, String in, Double money) throws IOException;
    
  • readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。

  • timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。

  • noRollbackFor:当出现指定异常不进行事务回滚

  • rollbackForClassName:等同于rollbackFor,只不过属性为异常的类全名字符串

  • noRollbackForClassName:等同于noRollbackFor,只不过属性为异常的类全名字符串

  • isolation设置事务的隔离级别(见MySQL数据库相关知识)

    • DEFAULT:默认隔离级别, 会采用数据库的隔离级别
    • READ_UNCOMMITTED : 读未提交
    • READ_COMMITTED : 读已提交
    • REPEATABLE_READ : 重复读取
    • SERIALIZABLE: 串行化

案例:转账业务追加案例

需求:无论转账操作是否成功,均进行转账操作的日志留痕
准备工作:基于前面5.2节的案例

步骤1:添加数据库表

create table tbl_log(id int primary key auto_increment,info varchar(255),createDate datetime
)

步骤2:添加LogDao接口

package org.example.dao;@Repository
public interface LogDao {@Insert("insert into tbl_log (info, createDate) values (#{info}, now())")void log(String info);
}

步骤3:添加LogService接口与实现类

package org.example.service;public interface LogService {@Transactionalpublic void log(String out, String in, Double money);
}
package org.example.service.impl;@Service
public class LogServiceImpl implements LogService {@Autowiredprivate LogDao logDao;@Overridepublic void log(String out, String in, Double money) {logDao.log("转账操作由"+out+"到"+in+",金额:"+money);}
}

步骤4:在转账的业务中添加记录日志

package org.example.service;public interface AccountService {    @Transactional    public void transfer(String out, String in, Double money);
}
package org.example.service.impl;@Service
public class AccountServiceImpl implements AccountService {@Autowired@Qualifier("accountDao")private AccountDao accountDao;@Autowiredprivate LogService logService;@Overridepublic void transfer(String out, String in, Double money) {try {accountDao.outMoney(out, money);int i = 1/0;accountDao.inMoney(in, money);} finally {logService.log(out, in, money);}}
}

注意:结果如果报异常,记录不会被写入tbl_log表中去,
因为此时日志记录和转账操作隶属于一个事务,同成功同失败,那么转账被回滚了失败了,日志记录自然也失败了

但是需求是:无论转账是否成功,都记录日志
此时需要:转账的两个操作inMoney和outMoney加入到transfer事务中,但记录日志的log操作单独启动一个事务

事务传播行为

修改日志的事务属性:propagation

package org.example.service;public interface LogService {@Transactional(propagation = Propagation.REQUIRES_NEW)//开启新事物public void log(String out, String in, Double money);
}
package org.example.service.impl;@Service
public class LogServiceImpl implements LogService {@Autowiredprivate LogDao logDao;@Overridepublic void log(String out, String in, Double money) {logDao.log("转账操作由"+out+"到"+in+",金额:"+money);}
}

此时即可实现失败转账操作回滚,但日志仍被记录
在这里插入图片描述


http://www.ppmy.cn/news/19669.html

相关文章

Coolify系列02-从0到1超详细手把手教你上手Coolify

重启 如果由于某种原因&#xff0c;你的实例崩溃了&#xff0c;你可以用下面的命令重新启动它: wget -q https://get.coollabs.io/coolify/install.sh \ -O install.sh; sudo bash ./install.sh -r防火墙设置 您需要在防火墙中允许以下端口 Coolify: 3000 (required)Revers…

【HBase入门】8. HBase Java编程(2)——创建表、插入数据、删除数据

前言 本文是HBase Java编程&#xff08;2&#xff09;——创建表、插入数据、删除数据。 需求一&#xff1a;使用Java代码创建表 创建一个名为WATER_BILL的表&#xff0c;包含一个列蔟C1。 实现步骤&#xff1a; 1.判断表是否存在——存在&#xff0c;则退出 2.使用TableDe…

【Python从入门到精通】第一阶段

文章目录前言python的起源打印hello world注释变量变量基本概念类型类型转换运算符字符串拓展字符串的三种定义方法字符串拼接字符串格式化数据输入input比较布尔类型和比较运算符if判断if elseif elif else嵌套循环while循环while循环嵌套for循环range()的使用函数的使用函数的…

17种编程语言+10种排序算法

开源地址 https://gitee.com/lblbc/simple-works/tree/master/sort 覆盖语言&#xff1a;C、C、C#、Java、Kotlin、Dart、Go、JavaScript(JS)、TypeScript(TS)、ArkTS、swift、PHP。 覆盖平台&#xff1a;安卓(Java、Kotlin)、iOS(SwiftUI)、Flutter(Dart)、Window桌面(C#)、前…

DFS(深度优先搜索)详解(概念讲解,图片辅助,例题解释)

目录 那年深夏 引入 1.什么是深度优先搜索&#xff08;DFS&#xff09;&#xff1f; 2.什么是栈&#xff1f; 3.什么是递归&#xff1f; 图解过程 问题示例 1、全排列问题 2、迷宫问题 3、棋盘问题&#xff08;N皇后&#xff09; 4、加法分解 模板 剪枝 1.简介 2.剪枝的…

HackTheBox Stocker API滥用,CVE-2020-24815获取用户shell,目录遍历提权

靶机地址&#xff1a; https://app.hackthebox.com/machines/Stocker枚举 使用nmap枚举靶机 nmap -sC -sV 10.10.11.196机子开放了22&#xff0c;80端口&#xff0c;我们本地解析一下这个域名 echo "10.10.11.196 stocker.htb" >> /etc/hosts 去浏览器访问…

Go语法知识笔记

文章目录前言一、结构分析二、变量&常量变量的基本类型&#xff1a;变量的定义常量的定义三、分支&循环if-elseswitch casefor循环四、数组五、函数六、切片七、 指针new创建指针其他八、结构体成员函数json转结构体九、错误处理十、标准库字符串操作字符串格式化json …

高阶数据结构 位图的介绍

作者&#xff1a;学习同学 专栏&#xff1a;数据结构进阶 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍高阶数据结构位图 位图的介绍bitset的介绍位图的引入位图的概念位图的引用bitset的使用bitset定义方式方式一 默认初…