三、AOP
1.AOP 的基本概念
AOP(Aspect-Oriented Programming)是一种编程范式,它将关注点分离出来,以便更好地管理代码。
在传统的面向对象编程中,关注点(如安全性、事务处理等)是混杂在业务逻辑中的。这使得代码难以理解和维护,因为关注点与业务逻辑交织在一起。,导致模块之间的耦合度过高。
AOP通过将关注点分离出来,将它们封装在称为“切面”的模块中,使得业务逻辑和关注点分离,从而提高了代码的可读性和可维护性。 在Java中,Spring框架提供了AOP的支持,可以通过在代码中使用特定的注解和配置来实现AOP编程。
1)通过登录的例子来理解 AOP
解析
上面的例子中,我们首先实现了一个通过数据库来验证登录是否成功的一个模块,但是这时候如果我们想要在运行的时候对这个用户进行权限判断,按照之前的编程思路,肯定是通过修改登录模块的源代码来实现的,可通过 AOP 可以实现不修改源代码而增加新的功能。
2.AOP 的底层原理
AOP 的底层用到了动态代理的技术,先来了解一下什么是动态代理:
动态代理是一种在运行时创建一个类和它的实例的技术。它是在运行时动态生成的类,通常是为了实现某些特定的功能。 动态代理通常使用Java的反射 API 来创建类和实例。反射API允许我们在运行时访问类和对象的信息,并且可以创建类的实例,调用类的方法和访问类的属性。
动态代理的一个常见用途是实现AOP(面向切面编程)。通过使用动态代理,可以将关注点(如安全性、事务处理等)封装在称为“切面”的模块中,使得业务逻辑和关注点分离,从而提高了代码的可读性和可维护性。
有两种情况,分别是有接口的情况下使用 JDK 动态代理,和没有接口的情况下使用 CGLIB 动态代理
3.AOP:JDK 动态代理
-
使用 JDK 动态代理,就需要 Proxy 这个类能提供给我们创建代理对象的方法。
static Object newProxyInstance(ClassLoader loader, 类<?> interfaces, InvocationHandler h) //返回指定接口代理类的实例,该接口将方法调用分派给指定的调用处理程序
有三个参数:类加载器、增强方法所在的类和这个实现的接口,支持多个接口、实现这个接口 InvocationHandler 创建代理对象,写增强的部分
-
JDK 动态代理的实现
public class UserTest { public static void main(String[] args) { Class[] classes = {UserInterface.class}; User user = new User(); UserProxy userProxy = new UserProxy(user); UserInterface o = (UserInterface)Proxy.newProxyInstance(User.class.getClassLoader(), classes, userProxy); o.add(1, 2); } } class UserProxy implements InvocationHandler { private Object obj; public UserProxy(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; } }
解析
通过上面的代码,我们实现了代理类的创建和方法的调用,对接口 InvocationHandler 有更深的理解,它实现了方法并且能够在方法的前后去添加新的操作,这部分的代码属于底层原理,暂时不需要完全理解。
4.AOP 的常用术语
- 连接点:类里面的哪些方法是可以被增强的,这些方法称为连接点
- 切入点:实际上被增强的方法称为切入点
- 通知:实际增强的逻辑部分称为通知,通知有多种类型:前置通知、后置通知、环绕通知、异常通知、最终通知。
- 切面:将通知应用加入到切入点的过程,是一种动作。
5.AOP 操作:准备
Spring 框架一般都是基于 AspectJ 实现 AOP 操作,AspectJ 并不是 spring 的组成部分,而是一个独立的 AOP 框架,一般把 AspectJ 和 Spring 框架一起使用,进行 AOP 操作。
基于 AspectJ 实现 AOP 操作,分为基于 xml 配置文件和注解方式,一般使用注解方式。
在项目工程中导入相关的依赖
在配置的阶段还需要了解切入点表达式,它可以帮助我们知道对哪个类中的哪个方法进行了增强。
语法结构:execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表]) // 以下是一些举例 // 对 com.atguigu.dao.BookDao类中的add方法进行增强 execution(* com.atguigu.dao.BookDao.add(..)) // 对 com.atguigu.dao.BookDao 类中的所有方法进行增强 execution(* com.atguigu.dao.BookDao.*(..)) // 对 com.atguigu.dao 所有类中的所有方法进行增强 execution(* com.atguigu.dao.*.*(..)) // * 表示所有的权限修饰符,返回类型可以省略
6.AOP 操作:AspectJ 注解
-
创建类,并且在类中定义方法
@Component public class User{ public int add(int a, int b) { System.out.println("add 方法执行"); return (a + b); } }
-
创建增强类,增强类里面创建方法,让不同的方法代表不同的通知类型
//注意一定需要上面的注解,要不然该类不会被构建 @Component @Aspect public class UserProxy { @Before(value = "execution(* com.atguigu.spring5.User.add(..))") public void before() { System.out.println("前置通知"); } @AfterReturning(value = "execution(* com.atguigu.spring5.User.add(..))") public void afterReturn(){ System.out.println("后置通知(返回通知)"); } @After(value = "execution(* com.atguigu.spring5.User.add(..))") public void after() { System.out.println("最终通知"); } @AfterThrowing(value = "execution(* com.atguigu.spring5.User.add(..))") public void afterThrow() { System.out.println("异常通知"); } @Around(value = "execution(* com.atguigu.spring5.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前"); proceedingJoinPoint.proceed(); System.out.println("环绕之后"); } }
-
执行顺序
环绕之前 前置通知 方法本身的执行 环绕之后 后置通知 最终通知,return 之后
4. 进行通知的配置,在 spring 的配置文件中开启注册扫描
创建 context 域,然后利用其中的 <context:component-scan base-package="com.atguigu"/> 来扫描
5. 配置不同类型的通知:已经在上面的代码中提前配好了
6. 配置开启代理对象的代码
```xml
<aop:aspectj-autoproxy/>
经过上面的步骤,我们发现每个代理方法的前面都有一个相同的 value 值,有没有方法能够简化写法呢?
@Pointcut(value = "execution(* com.atguigu.spring5.User.add(..))")
public void pointDemo() {}
@Before(value = "pointDemo()")
public void before() {
System.out.println("前置通知");
}
通过在方法前面加上注解的方式来提取使用多次的方法,这样就可以简化写法。
再解决下一个细节,当有多个增强类对同一个方法进行增强的时候,如何设置优先级?
//在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
@Component
@Aspect
@Order(1)
public class PersonProxy{}
7.AOP 操作:配置文件
<!--创建对象-->
<bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
<!--配置 aop 增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(*com.atguigu.spring5.aopxml.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
四、JDBCTemplate
1.概述和准备工作
JDBCTemplate
是Spring框架中的一个类,它是一个JDBC操作的模板类。JDBCTemplate
封装了JDBC操作的大部分细节,使得开发人员可以更简单、更方便地进行数据库操作。
JDBCTemplate
提供了各种方法,可以用于执行SQL语句、获取结果集、处理事务等。它还提供了一些方法,可以用于处理SQL语句的异常,例如execute()
方法和update()
方法。
使用JDBCTemplate
可以大大简化JDBC操作,避免了许多JDBC操作中的常见错误,例如SQL注入、资源泄漏等。同时,JDBCTemplate
还支持自动处理事务,可以大大简化事务处理的代码。
1)准备工作
引入相关的 jar 包
在 spring 配置文件中配置 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="123456" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
配置 JdbcTemplate 对象,并向其中注入 dataSourse
<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
创建 dao 类,在 dao 中注入 JdbcTemplate 对 象
@Repository
public class BookDaoImpl implements BookDao {
//注入 JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
2.JdbcTemplate 实现数据库操作
1)实现数据库的添加操作
-
先来了解以下 jdbcTemplate 的 update方法:
(int) jdbcTemplate.update(String sql, Object[] args) // 返回的是影响的行数,Object 数组中存放的是 sql 语句中 ? 代表的数据 // 它用于执行SQL更新操作(如INSERT、UPDATE、DELETE)
-
创建 dao 类和 service 类
//这个注解表示他是连接数据库处理数据的组件 @Repository public class BookDaoImpl implements BookDao { //注入 JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; //添加的方法 @Override public void add(Book book) { //1 创建 sql 语句 String sql = "insert into t_book values(?,?,?)"; //2 调用方法实现 Object[] args = {book.getUserId(), book.getUsername(), book.getUstatus()}; int update = jdbcTemplate.update(sql,args); System.out.println(update); }
@Test public void testJdbcTemplate() { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); BookService bookService = context.getBean("bookService", BookService.class); Book book = new Book(); book.setUserId("1"); book.setUsername("java"); book.setUstatus("a"); bookService.addBook(book); }
2)实现数据库的修改和删除操作
修改和删除操作也是只返回影响的行数的,我们仍可以用 update 方法完成上述的操作。
//1、修改
@Override
public void updateBook(Book book) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
Object[] args = {book.getUsername(), book.getUstatus(),book.getUserId()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
//2、删除
@Override
public void delete(String id) {
String sql = "delete from t_book where user_id=?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
3)查询返回的是某个值
查询返回某个值即返回的是一行一列,可以使用到 queryForObject 方法去实现
(T) queryForObject(String sql, Object[] args, Class<T> requiredType)
// 这个方法有三个个参数,分别是执行的 sql 语句和传入 ? 的参数
// 以及需要返回的值的 class 值
下面来看具体的实现,我们希望返回表中记录的总条数,可以知道返回的是一个数据。
public int selectCount() {
String sql = "select count(*) from t_book";
Integer count = jdbc.queryForObject(sql, Integer.class);
return count;
}
4)查询返回对象
如果返回的是一个对象的话则需要指定返回的的类型。
先来介绍一下其中的 queryForObject 方法
(T) queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
// 先来解析以下其中的三个属性
// 先传入要执行的 sql 语句,这里执行的是返回一个对象的操作
// 第二个参数是一个 RowMapper 对象,这是一个接口定义了一个方法,可以将返回的结果映射为一个对象。
// 第三个参数是 ? 中需要传入的数据
// 则通过上面的方法可以实现返回一个对象,通过 rowMapper 来将返回的结果映射为对象
// 实现了通过 id 来返回对象的方法
@Override
public Book findBookInfo(String id) {
String sql = "select * from t_book where user_id=?";
// 调用方法
Book book = jdbcTemplate.queryForObject(sql,
new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
5)查询返回集合
先来看一个应用场景:在图书管理系统中,我们需要通过 limit 语句限制查询的返回数来达到分页的功能,这时候,就需要返回一个 book 的 list 对象。
(T) query(String slq, RowMapper<T> rwoMapper, Object... args)
// 这里有三个返回的参数,具体的效果和查询返回对象的时候是相同的,但方法是 query,可以返回一个集合
//查询返回集合
@Override
public List<Book> findAllBook() {
String sql = "select * from t_book";
//调用方法,这里的 BeanPropertyMapper 是 MapperRow 接口的实现对象,用于将返回结果映射为对象
List<Book> bookList = jdbcTemplate.query(sql,
new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
6)实现批量添加
批量添加可以提高数据库添加大量数据的速度,spring 中也定义了响应的方法去实现
int batchUpdate (String sql, List<Object> batchArgs)
// 有两个参数
// 第一个参数是一个 sql 语句
// 第二个参数是一个 list 集合,添加多条记录的数据
//批量添加
@Override
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values(?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
public static void main(String[] args) {
//批量添加测试
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3","java","a"};
Object[] o2 = {"4","c++","b"};
Object[] o3 = {"5","MySQL","c"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
//调用批量添加
bookService.batchAdd(batchArgs);
}
7)实现批量修改的操作
//批量修改
@Override
public void batchUpdateBook(List<Object[]> batchArgs) {
// 批量修改操作返回的是一个数组类型,表示每次操作影响的行数
String sql = "update t_book set username=?,ustatus=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
8)实现批量删除的操作
// 批量删除
@Override
public void batchDeleteBook(List<Object[]> batchArgs) {
String sql = "delete from t_book where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints))
}
// 测试方法
public static void main(String[] args) {
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3"};
Object[] o2 = {"4"};
batchArgs.add(o1);
batchArgs.add(o2);
bookService.batchDeleteBook(batchArgs);
}