Spirng5 | AOP基础

1.AOP概述

  • 面向切面编程,在不修改源代码的情况下利用AOP对业务逻辑的各个部分进行隔离,达到低耦合性的效果
  • Spring框架一般是基于aspectJ实现AOP操作,Aspectj也是框架,但一般把两者结合进行AOP操作

1.1切入点表达式

  • 其作用是为了明确哪个类里面的哪个方法进行增强
  • 语法结构:execution([权限修饰符][返回值类型][类全路径][方法名称][参数列表])
    1
    2
    3
    4
    //以com.aop.Book为例 *代表所有的意思
    excution(* com.aop.Book.add(..)) //对此类里面的add方法进行增强
    excution(* com.aop.Book.*(..)) //对此类里面的所有方法进行增强
    excution(* com.aop.*.*(..)) //对aop包下的所有方法进行增强

1.2动态代理

  1. 创建一个接口定义一组方法及它的实现类
  2. 通过Proxy的newProxy方法获取代理类对象
  3. 定义一个类实现InvocationHandler接口
  4. 创建的是谁的对象,把谁传递进来,通过有参构造的方式
  5. 把需要增强的逻辑写在重写的invoke方法里
  6. 返回被增强的方法

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//本类加载器
ClassLoader clr = 本类类名称.class.getClassLoader();

//被代理接口
Class[] interfaces = {UserDao.class};

//被代理接口实现类
UserDao userDao = new UserDaoImpl();

//代理类
UserDaoProxy udp = new UserDaoProxy(userDao);

//获取接口代理类对象
UserDao result = (UserDao) Proxy.newProxyInstance(clr, interfaces, udp);
System.out.println("result:"+result.add(1, 2));

代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UserDaoProxy implements InvocationHandler{
//1.把创建的是谁的对象,把谁传递进来
//有参数构造传递,可以为指定类或Object
private Object obj;
public UserDaoProxy(Object obj){
this.obj = obj;
}
//增强的逻辑写在invoke方法里面
@Override
public Object invoke(Object proxy,
Method method, Object[] args){
//返回被增强的方法
return method.invoke(obj, args);
}

1.3操作术语

  • 连接点:类中可以增强的方法
  • 切入点:实际被增强的方法
  • 通知(增强):实际增强的逻辑部分
  • 分为:前置、后置、环绕、异常、最终通知
  • 切面:通知应用到切入点的过程

2.AOP操作

2.1通知注解

  1. 有异常也执行(最终通知)
    1
    @After(value = "切入点表达式")
  2. 有异常就不执行了,方法返回结果后执行(后置通知)
    1
    @AfterReturning(value = "切入点表达式")
  3. 有异常时才执行(异常通知)
    1
    @AfterThrowing(value = "切入点表达式)")
  4. 配合ProceedingJoinPoint方法(环绕通知)
    1
    2
    3
    4
    5
    6
    7
    @Around(value = "切入点表达式")
    public void Around(ProceedingJoinPoint joinPoint){
    System.out.println("环绕前通知");
    //代表被增强的方法执行
    joinPoint.proceed();
    System.out.println("环绕后通知");
    }

2.2基于注解

配置准备

  1. 创建xml文件引入名称空间
  2. 在xml中开启注解扫描和aspectJ生成代理对象
  3. 创建被增强类与增强类,添加@compment与@Aspect(仅增强类添加)
  4. 在增强类中创建方法,根据需求添加通知注解,并填入切入点表达式
  5. 使用junit进行测试

xml配置

1
2
3
4
5
6
7
8
//xml文件配置
//1.引入名称空间
xmlns:context
xmlns:aop
//2.开启注解扫描
<context:component-scan base-package="类路径"/>
//3.开启aspectJ生成代理对象
<aop:aspectj-autoproxy/>

实体类配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//被增强类
@Compment
public void User(){show方法}
//增强类
@Compment
@Aspect
public void UserProxy{
//value里面为切入点表达式
@Before(value = "execution(* com.User.show(..))")
public void before(){
System.out.println("前置通知");
}
}
//增强类2 类名上注解同上
public class StudentProxy {
@Before(value = "execution(* com.User.show(..))")
@Order(0) //此注解设置优先级 数字小等级高
public void show(){
System.out.println("我使用了order我优先");
}
}

切入点抽取

  • 当多个通知注解相同时可以使用切入点抽取注解
  • 在通知注解value填入抽取注解的方法即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //相同的切入点抽取
    @Pointcut(value ="execution(* com.User.show(..))")
    public void userPoint(){};

    //使用切入点抽取方法
    @After(value = "userPoint()")
    public void after(){
    System.out.println("最终通知");
    }

    @AfterReturning(value = "userPoint()")
    public void AfterReturning(){
    System.out.println("后置通知");
    }

3.JDBCTemplete

  1. org.springframework.jdbc.core.JdbcTemplate类是JDBC核心包中的中心类
  2. 它简化了JDBC的使用,并有助于避免常见的错误
  3. 它执行核心JDBC工作流,留下应用程序代码来提供SQL并提取结果

3.1依赖准备

Druid线程池和JDBC的Maven依赖

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>

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

xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.引入名称空间context
2.开启组件扫描
<context:component-scan base-package="包路径"/>
3.引入数据库连接
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/bank"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
4.jdbcTemplate对象
<bean id="database" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入DataSource-->
<property name="dataSource" ref="druid"/>
</bean>

3.2创表语句

1
2
3
4
5
6
7
CREATE TABLE `book_info` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(30) DEFAULT NULL,
`money` DECIMAL(9,2) DEFAULT NULL,
`password` VARCHAR(60) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8

3.3增加数据

DAO

1
2
3
4
5
6
7
8
9
void addBook(Book book);
DAOImpl类
@Override
public void addBook(Book book) {
String sql = "insert into book_info values(?,?,?,?)";
int add= jdbcTemplate.update(sql, book.getId(), book.getName(), book.getMoney(), book.getPassword());
System.out.println(sql);
System.out.println("添加成功:影响行数"+add);
}

Service类

1
2
3
4
5
6
7
8
9
10
11
@Resource        //注入DAO
private BookDao bookDao;
public void addBook(Book book){
bookDao.addBook(book);}
功能Junit测试
final Book book = new Book();
book.setId(8);
book.setName("背包十年");
book.setMoney(39.5);
book.setPassword("123456");
bs.addBook(book);

读取xml文件建立数据库连接(增删改查都需要)

1
2
3
4
ApplicationContext context = 
new ClassPathXmlApplicationContext("druidConfig.xml");
final BookService bs =
context.getBean("bookService", BookService.class);

3.4删除数据

DAO 根据id删除

1
2
3
4
5
6
7
8
void delBook(String id);
DaoImpl类
@Override
public void delBook(String id) {
String sql = "delete from book_info where id=?";
final int del = jdbcTemplate.update(sql, id);
System.out.println("删除成功:影响行数"+del);
}

Service类

1
2
3
4
5
6
7
@Resource    //注入DAO
private BookDao bookDao;
public void delBook(String id){
bookDao.delBook(id);
}
Junit测试
bs.delBook("8");

3.5修改数据

DAO

1
2
3
4
5
6
7
8
void updateBook(Book book);
DaoImpl类
@Override
public void updateBook(Book book) {
String sql = "update book_info set name=?,money=?,password=? where id =?";
final int update = jdbcTemplate.update(sql, book.getName(), book.getMoney(), book.getPassword(), book.getId());
System.out.println("修改成功:影响行数"+update);
}

Service类

1
2
3
4
5
6
7
8
9
10
11
12
@Resource    //注入DAO
private BookDao bookDao;
public void updateBook(Book book){
bookDao.updateBook(book)
}
Junit测试
final Book book2 = new Book();
book2.setId(8);
book2.setName("骆驼祥子");
book2.setMoney(19.5);
book2.setPassword("123456");
bs.updateBook(book2);

3.6查询表记录数

DAO

1
2
3
4
5
6
7
int queryCount();
//DAO Impl
@Override
public int queryCount() {
String sql = "select count(*) from book_info";
return jdbcTemplate.queryForObject(sql, Integer.class);
}

Service

1
2
3
4
5
6
public void queryCount(){
final int count = bookQuery.queryCount();
System.out.println("数据库共存在"+count+"条数据");
}
//JunitTest
bs.queryCount();

3.7查询指定数据

BeanPropertyRowMapper

  1. 将数据库查询结果转换为Java类对象
  2. 常应用于使用Spring的JdbcTemplate查询数据库
  3. 获取List结果列表,数据库表字段和实体类自动对应

DAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//查询返回对象 根据id
Book findBookInfo(String id);
//查询返回集合
List<Book> findAll();
//DAO Impl
@Override
public Book findBookInfo(String id) {
String sql = "select * from book_info where id=?";
//RowMapper接口 根据返回值类型 实现类完成数据封装
final Book book =
jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
@Override
public List<Book> findAll() {
String sql = "select * from book_info;";
final List<Book> query =
jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
return query;
}

Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Book findOne(String id){
return bookQuery.findBookInfo(id);
}
public List<Book> findAll(){
return bookQuery.findAll();
}
//JunitTest
//查询返回对象
final Book one = bs.findOne("2");
System.out.println(one);

//查询返回集合(所有数据)
final List<Book> allBook = bs.findAll();
allBook.forEach(System.out::println);

4.事务操纵

  1. 事务的操纵一般写在Service层
  2. 事务的管理操纵分为编程式和声明式
  3. 声明式为常用方式其又分为注解方式和xml方式
  4. Spring中事务的底层使用AOP原理

4.1事务API

  • 提供了一个接口,代表事务管理器
  • 此接口针对不同框架类型提供了不同的实现类
  • 针对jdbc使用DataSourceTransactionManager类

PlantTransactionManager针对不同数据库提供的管理器

4.2基于注解

Bean文件

1
2
3
4
5
6
7
8
9
10
//基本配置:名称空间tx,context,aop,druid数据库连接池
//1.创建事务管理器
<bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
//<!--1.1注入数据源-->
<property name="dataSource" ref="druid"/>
</bean>
//2.开启事务注解
<tx:annotation-driven transaction-manager="TransactionManager"/>
//3.在service层使用@Trasactional注解开启事务
//写在类面或方法名上,写在方法名上代表开启此方法事务

事务java类

1
2
3
@Service		//创建对象
@Transactional //开启事务
public class UserService {}

4.3基于xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//基本配置:名称空间tx,context,aop,druid数据库连接池
//1.创建事务管理器
<bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
//2.注入数据源
<property name="dataSource" ref="druid"/>
</bean>
//3.配置通知
<tx:adviceid="txadvice">
<!--3.1配置事务参数-->
<tx:attributes>
<!--3.2指定哪种规则的方法上面添加事务-->
<tx:methodname="transfer"propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!--4.配置切入点与切面-->
<aop:config>
<!--4.1切入点-->
<aop:pointcutid="cut"expression="execution(*affairs.service.UserService.*(..))"/>
<!--4.2切面-->
<aop:advisoradvice-ref="txadvice"pointcut-ref="cut"/>
</aop:config>

5.事务参数配置

在service添加@Transactional,所有配置都在此注解内

6种参数配置

1
2
//配置使用
@Transactional(timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)

5.1传播行为

  • Propagation即为事务的传播行为
  • 当一个事务方法被另一个事务方法调用时候,这个事务方法如何进行。

三种常用传播行为

5.2隔离级别

  1. 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
  2. 有三个读问题:脏读、不可重复读、虚(幻)读
  3. 脏读:一个未提交事务读取到另一个未提交事务的数据
  4. 不可重复读:一个未提交事务读取到另一提交事务修改数据
  5. 虚读:一个未提交事务读取到另一提交事务添加数据
  6. 解决:通过设置事务隔离级别,解决读问题

隔离级别

5.3其他配置

Timeout
事务需要在一段时间内进行提交,如果不提交进行回滚
默认值为-1,设置单位为s

ReadOnly
默认为false,可以指定为true代表只读,不可做增删改查

RollbackFor 出现哪些异常回滚

NoRollbackFor 出现哪些异常不回滚

6.新特性

  1. Spring5框架基于JDK8
  2. 自带通用日志封装,官网建议使用log4j2
  3. @Nullable注解,可用在方法,属性或参数上,表示可为空
  4. 支持函数式风格GenericApplicationContext
  5. 整合Junit测试

6.1log4j2日志

  1. 导入log4j依赖
  2. 创建log4j2.xml文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration status="INFO">
    <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并引入appender,appender才会生效-->
    <loggers>
    <root level="info">
    <appender-ref ref="Console"/>
    </root>
    </loggers>
    </configuration>

6.2函数式风格

  1. 创建一个java类
  2. 创建测试方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //1.创建GenericApplicationContext对象
    GenericApplicationContext cont = new GenericApplicationContext();
    //2.调用cont中的方法对象注册
    cont.refresh();

    //Lambda表达式
    //cont.registerBean(Lambda.class,() -> new Lambda());

    //方法引用
    cont.registerBean("ltd",Lambda.class, Lambda::new);
    //3.获取spring的注册对象
    Lambda ltd = (Lambda) cont.getBean("ltd");
    ltd.info();
    @Nullable
    1
    2
    //表示当前方法形参可为空
    void message(@Nullable String value){}

6.3Junit4整合

通过Junit整合配置Auntowired注解可以减少代码量

1
2
3
4
5
6
7
8
9
10
11
@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架
@ContextConfiguration("classpath:Bean1.xml") //加载配置文件
public class JTest4 {
@Autowired
private UserService us;
@Test
public void test1(){
//自动创建对象后直接调用方法
us.transfer();
}
}

6.4Junit5整合

1
2
3
4
5
6
7
8
9
10
11
//@ExtendWith(SpringExtension.class)//单元测试框架
//@ContextConfiguration("classpath:Bean1.xml") //加载配置文件
@SpringJUnitConfig(locations = "classpath:Bean1.xml") //上面两个注解的结合
public class JTest5 {
@Autowired
private UserService us;
@Test
public void test1(){
us.transfer();
}
}

Spirng5 | AOP基础
http://example.com/2022/07/27/Spring5/AOP基础/
Author
John Doe
Posted on
July 27, 2022
Licensed under