SpringAOP

AOP简介和作用

  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构,而 OOP(Object Oriented Programming) 是面向对象编程,AOP是对OOP的一种补充
  • 作用:简单的说就是在不改变方法源代码的基础上对方法进行功能增强

AOP的一些概念

  • 连接点(JoinPoint):正在执行的方法,例如:update()、delete()、select()等都是连接点
  • 切入点(Pointcut):进行功能增强了的方法,例如:update()、delete()方法,select()方法没有被增强所以不是切入点,但是是连接点
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
  • 通知(Advice):在切入点前后执行的操作,也就是增强的共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:通知方法所在的类叫做通知类
  • 切面(Aspect):描述通知与切入点的对应关系,也就是哪些通知方法对应哪些切入点方法
image-20210730144903209.png
image-20210730154740528.png

SpringAOP快速入门

  • 导入依赖
<dependencies>
    <!--spring-context-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>

    <!--aspectjweaver(spring面向切面开发必须的第三方组件)-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>

    <!--junit,spring对junit4的要求必须是4.12版本以上-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>

    <!--spring-test-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
</dependencies>
  • 定义Dao接口与实现类
//Dao层接口类
public interface BookDao {
    void save();

    void update();

    void delete();
}

//Dao层实现类
@Repository
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao save ...");
    }

    @Override
    public void update() {
        System.out.println("book dao update ...");
    }

    @Override
    public void delete() {
        System.out.println("book dao delete ...");
    }
}
  • 定义通知类,编写通知方法
/**
 * 通知类
 * 注意:需要将切面类加入Spring-IOC容器
 */
@Component //需要将切面类加入Spring-IOC容器
@Aspect //指定为切面类,Spring框架就会对这个类进行AOP操作
public class MyAdvice {
    /**
     * 定义切入点表达式,指定对 BookDaoImpl 的 save()、update()方法进行增强
     */
    //最完整的写法,必须3个参数必写:返回值、方法名、参数
    @Pointcut("execution(void com.itheima.dao.BookDao.save()) || execution(void com.itheima.dao.BookDao.update())")
    public void pt() {
    }

    /**
     * 通知方法
     * <p>
     * 注解@Before,前置通知,会在业务方法前,调用该增强方法
     * 属性 @Before("pt()"),绑定切入点,目的是让增强方法和业务方法绑定关系
     */
    @Before(value = "pt()")
    public void before() {
        System.out.println("写入日志");
    }
}
  • 在Spring配置类中开启AOP注解扫描
@Configuration
@ComponentScan("com.itheima")
//开启AOP注解扫描(开启后,才会扫描AOP注解,才能使用AOP面向切面功能)
@EnableAspectJAutoProxy
public class SpringConfig {
}
  • 编写测试类,测试AOP
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookDaoTest {
    /**
     * 注入BookDao,这个对象是代理对象,AOP底层就是动态代理,调用方法时,先调用增强的代码,再调用我们的业务代码
     */
    @Autowired
    private BookDao bookDao;

    @Test
    public void test() {
        System.out.println("运行 =======> 需要增强的方法");
        bookDao.save();
        bookDao.update();

        System.out.println("运行 =======> 执行没有增强的方法");
        bookDao.delete();
    }
}

AOP工作流程

  • Spring容器启动
  • 读取所有切面配置中的切入点
  • 初始化bean,判断bean对应的类中的方法是否匹配到任意切入点
    • 匹配失败,创建原始对象
    • 匹配成功,创建原始对象(目标对象)的代理对象
  • 获取bean执行方法
    • 获取的bean是原始对象时,调用方法并执行,完成操作
    • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

AOP核心概念

  • 目标对象(Target):被代理的对象,也叫原始对象,该对象中的方法没有任何功能增强
  • 代理对象(Proxy):代理后生成的对象,由Spring帮我们创建代理对象

AOP切入点表达式

语法格式

  • 切入点:要进行增强的方法
  • 切入点表达式:要进行增强的方法的描述方式
  • 描述方式一:执行com.itheima.dao包下的BookDao接口中的无参数update方法
execution(void com.itheima.dao.BookDao.update())
  • 描述方式二:执行com.itheima.dao.impl包下的BookDaoImpl类中的无参数update方法
execution(void com.itheima.dao.impl.BookDaoImpl.update())
  • 切入点表达式标准格式:切点函数(访问修饰符 返回类型 类全名.方法名(参数类型) 异常类型)
execution(public User com.itheima.service.UserService.findById(int))
  • 切点函数:描述切入点的行为动作,例如execution表示执行到指定切入点
  • 访问修饰符:public,private等,可以省略
  • 返回值类型
  • 包名:多级包使用点连接
  • 类全名:包名+类名/接口名
  • 参数:直接写参数的类型,多个类型用逗号隔开
  • 异常名:方法定义中抛出指定异常,可以省略

通配符

  • 目的:可以使用通配符描述切入点,快速描述

通配符:*

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

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

execution(public * com.itheima.*.UserService.find*(*))

通配符 ..

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

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

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

通配符 +

  • + 专用于匹配子类类型
execution(* *..*Service+.*(..))

书写技巧

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

AOP通知类型

AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置

AOP通知共分为5种类型

  • 前置通知:在切入点方法执行之前执行
  • 最终通知:在切入点方法执行之后执行,无论切入点方法内部是否出现异常,最终通知都会执行
  • 环绕通知:手动调用切入点方法并对其进行增强的通知方式。
  • 后置通知:在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行
  • 异常通知:在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行

AOP通知详解

前置通知

  • 名称:@Before
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
@Before("pt()")
public void before() {
    System.out.println("before advice ...");
}

最终通知

  • 名称:@After
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
@After("pt()")
public void after() {
    System.out.println("after advice ...");
}

后置通知

  • 名称:@AfterReturning
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
@AfterReturning("pt()")
public void afterReturning() {
    System.out.println("afterReturning advice ...");
}

异常通知

  • 名称:@AfterThrowing
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
@AfterThrowing("pt()")
public void afterThrowing() {
    System.out.println("afterThrowing advice ...");
}

环绕通知

  • 名称:@Around(重点,常用)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around before advice ...");
    Object ret = pjp.proceed();
    System.out.println("around after advice ...");
    return ret;
}

环绕通知注意事项

  • 环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。
  • 环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里。

AOP切入点数据获取

获取参数

  • 前置通知环绕通知中都可以获取到连接点方法的参数
  • JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
  • ProccedJointPoint是JoinPoint的子类

前置通知中,获取方法参数

@Before("pt()")
public void before(JoinPoint jp) {
    Object[] args = jp.getArgs(); //获取连接点方法的参数们
    System.out.println(Arrays.toString(args));
}

环绕通知中,获取方法参数

@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    Object[] args = pjp.getArgs(); //获取连接点方法的参数们
    System.out.println(Arrays.toString(args));
    Object ret = pjp.proceed();
    return ret;
}

获取返回值

  • 后置通知环绕通知中都可以获取到连接点方法的返回值
  • 后置通知可以获取切入点方法的返回值,使用形参接收返回值

异常通知中,获取返回值

@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) { //变量名要和returning="ret"的属性值一致
    System.out.println("afterReturning advice ..."+ret);
}

环绕通知中,获取返回值

@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    // 手动调用连接点方法,返回值就是连接点方法的返回值
    Object ret = pjp.proceed();
    return ret;
}

获取异常信息

  • 异常通知环绕通知中都可以获取到连接点方法中出现的异常

异常通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象

@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {//变量名要和throwing = "t"的属性值一致
    System.out.println("afterThrowing advice ..."+ t);
}

异常通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象

@Around("pt()")
public Object around(ProceedingJoinPoint pjp)  {
    Object ret = null;
    //此处需要try...catch处理,catch中捕获到的异常就是连接点方法中抛出的异常
    try {
        ret = pjp.proceed();
    } catch (Throwable t) {
        t.printStackTrace();
    }
    return ret;
}

Spring事务管理

  • Spring事务管理器接口
image-20210801190820853.png

Spring事务作用

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

转账案例

  • 需求:实现任意两个账户间转账操作(A账户减钱,B账户加钱)

环境准备

  • 添加依赖
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.16</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.27</version>
    </dependency>

    <!-- Spring-JDBC依赖包,事务功能也在这里 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.0</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
</dependencies>
  • 创建表、并插入数据
-- 创建数据表,账户表
CREATE TABLE tbl_account (
    id INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(10),
    money DOUBLE  -- 金额
);

-- 添加数据
INSERT INTO tbl_account (NAME, money) VALUES ('Jack', 1000), ('Rose', 1000);

SELECT * FROM tbl_account;

-- 还原金额
update tbl_account set money=1000;
  • resources目录下创建jdbc.properties配置文件,配置JDBC相关信息
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/springdb2?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
  • 配置Spring,并且开启事务注解扫描(重点)
//Spring配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
//注解 @EnableTransactionManagement 开启事务注解扫描(当前包名下都扫描,所以不需要配置包名)
@EnableTransactionManagement
public class SpringConfig {
}
  • 配置JDBC以及Druid连接池
//Druid连接池配置
public class JdbcConfig {
    @Value("${jdbc.driverClassName}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driverClassName);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

    /**
     * 配置spring提供的事务切面类:里面增强了事务管理功能,里面有事务提交和事务回滚功能
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager ptm = new DataSourceTransactionManager();
        ptm.setDataSource(dataSource);
        return ptm;
    }
}
  • 配置MyBatis
//MyBatis配置类
public class MybatisConfig {
    @Bean  //不仅可以将返回值加入IOC容器,而且可以实现方法参数进行依赖注入,参数默认会根据类型从IOC容器中获取对象自动注入
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain"); //告诉mybatis,设置实体类包别名
        ssfb.setDataSource(dataSource); //将IOC容器中连接池给到Mybatis

        //注意:必须导入org.apache.ibatis.session.Configuration;
        Configuration configuration = new Configuration();
        //设置整合mybatis驼峰命名映射
        configuration.setMapUnderscoreToCamelCase(true);
        //设置打印日志
        configuration.setLogImpl(StdOutImpl.class);

        ssfb.setConfiguration(configuration);
        return ssfb;
    }

    //设置事务管理器,并将事务管理器添加到IOC容器中
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        //告诉mybatis扫描指定的接口映射包
        mapperScannerConfigurer.setBasePackage("com.itheima.dao");
        return mapperScannerConfigurer;
    }
}

编写代码

  • 编写数据持久层(Dao层)
public interface AccountDao {
    /**
     * 转入
     *
     * @param name  账号名
     * @param money 金额
     */
    @Update("update tbl_account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    /**
     * 转出
     *
     * @param name  账号名
     * @param money 金额
     */
    @Update("update tbl_account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}
  • 创建业务层接口以及实现,在接口的transfer转账方法添加@Transactional注解,让类方法加入Spring事务
//业务层接口
//(推荐这种)写到类头,那么所有实现的所有方法,都有了事务
@Transactional
public interface AccountService {
    /**
     * 转账
     *
     * @param out   转出给谁
     * @param in    转入到谁
     * @param money 金额
     */
    //在方法上,添加 @Transactional 注解,那么所有的实现类的这个方法都有了事务
    //@Transactional
    void transfer(String out, String in, Double money) throws IOException;
}

//业务层实现类
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    public void transfer(String out, String in, Double money) throws FileNotFoundException {
        accountDao.outMoney(out, money);
        //如果没有事务,这个异常会导致结束程序,导致下面的转入没有执行
        //int a = 1 / 0;
        accountDao.inMoney(in, money);
    }
}

注意事项

  • Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
  • 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务

Spring事务角色

  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
  • 事务协调员:加入事务方,在Spring中通常指数据层方法,也可以是业务层方法
image-20210801192453227.png

Spring事务相关配置

  • 对于RuntimeException类型异常或者Error错误,Spring事务能够进行回滚操作。但是对于非运行时异常,Spring事务是不进行回滚的,所以需要使用rollbackFor属性来设置要回滚的异常
image-20210802151553053.png

Spring事务的传播行为

  • Spring事务的传播行为说的是,当多个事务同时存在的时候,Spring如何处理这些事务的行为
image-20210802153014296.png

传播行为的种类

  • require必须的
  • support支持
  • mandatory 强制托管
  • requires-new 需要新建
  • not -supported不支持
  • never从不
  • nested嵌套的

传播行为种类解释

  • PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

  • PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

  • PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

  • PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

Spring的事务传播行为案例

  • 转账业务追加日志
  • 需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行记录(A账户减钱,B账户加钱,数据库记录日志)

默认事务传播行为的问题

  • 问题:日志的记录与转账操作隶属同一个事务,同成功同失败,失败时,日志记录也进行了回滚
  • 目标:无论转账操作是否成功,日志必须保留
  • 解决方案:日志记录的事务传播行为设置为REQUIRES_NEW,即就是调用方的方法已有事务,也创建一个独立的事务,即使调用方失败进行回滚,也不会将日志记录的回滚
image-20210802153216460.png

环境准备

  • 创建日志表
CREATE TABLE tbl_log(
    id INT PRIMARY KEY AUTO_INCREMENT,
    info VARCHAR(255),
    create_date DATETIME
);

编写代码

  • 创建日志持久层接口(Dao层)
/**
 * 转账日志记录-持久层
 */
public interface LogDao {
    /**
     * 增加转账日志
     *
     * @param info 日志信息
     */
    @Insert("insert into tbl_log (info,create_date) values(#{info},now())")
    void log(String info);
}
  • 创建日志业务层接口,并设置日志事务的事务传播配置
/**
 * 转账日志业务层接口
 */
public interface LogService {
    /**
     * 日志方法,无论调用当前方法是否是否有事务,当前方法都创建一个新的事务
     * <p>
     * Propagation.REQUIRES_NEW,就是无论调用方是否有事务,都开启一个新的事务
     * 如果不设置传播行为,那么就会加入到调用方的事务,调用方出异常回滚了,就会将当前log记录也一起回滚!
     * 注解@Transactional(propagation = Propagation.REQUIRES_NEW)
     *
     * @param out   谁转出转出
     * @param in    转出给谁
     * @param money 金额
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void log(String out, String in, Double money);
}
  • 创建日志业务层实现类
/**
 * 转账日志记录-业务层实现类
 */
@Service
public class LogServiceImpl implements LogService {
    @Autowired
    private LogDao logDao;

    public void log(String out, String in, Double money) {
        logDao.log("转账操作由" + out + "到" + in + ",金额:" + money);
    }
}
  • 在转账业务层,增加插入转账日志的调用
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Autowired
    private LogService logService;

    public void transfer(String out, String in, Double money) throws FileNotFoundException {
        try {
            accountDao.outMoney(out, money);
            //如果没有事务,这个异常会导致结束程序,导致下面的转入没有执行
            //int a = 1 / 0;
            accountDao.inMoney(in, money);
        } finally {
            //调用日志业务,记录日志(调用了另外一个业务方法,要求是一个独立的事务,就是不加入当前事务)
            logService.log(out, in, money);
        }
    }
}
  • 编写测试类,进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Jack", "Rose", 500d);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容

  • SpringAOP-PPT SpringAOP视频 面向切面编程(AOP)通过提供另外一种思考程序结构的途经来弥补...
    Grasse阅读 325评论 0 0
  • 写过程序的都知道OOP即面向对象编程。 从最开始的面向过程编程,到后面的面向对象编程,程序的编写方式发生了重大的变...
    flydean程序那些事阅读 206评论 0 2
  • AOP全程Aspect Oriented Programming面向切面编程,是一种编程范式,用于指导开发者如何组...
    JunChow520阅读 208评论 0 2
  • 文章内容来源:拉勾教育Java高薪训练营(侵权联系删除) 一、什么是AOP AOP 为 Aspect Orient...
    烦远远阅读 156评论 0 1
  • springAop:面向切面的编程 应用场景:权限控制、事物管理、日志打印等等,就是在不同的方法中重复用到相同的代...
    HJJ_3c00阅读 331评论 0 0