1.1 AOP介绍
1.1.1 什么是AOP
-
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
-
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码
-
经典应用:事务管理、性能监视、安全检查、缓存 、日志等
-
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
-
AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
1.1.2 AOP实现原理
-
aop底层将采用代理机制进行实现。
-
接口 + 实现类:spring采用 jdk 的动态代理Proxy。
-
实现类:spring 采用 cglib字节码增强。
1.1.3 AOP术语【掌握】
-
target目标对象:所有被通知的对象都是目标对象,AOP会注意目标对象的变动,随时准备准备向目标对象“注入切面”。
-
Joinpoint连接点:所谓连接点是指那些可能被拦截到的方法,实际上是对象的一个操作。例如:所有的方法
-
PointCut切入点:已经被增强的连接点。切面与程序流程的“交叉点”便是程序的切入点,切面是通过切入点被注入的。
-
advice通知:增强代码,通知时某个切入点被横切后,所采取的处理逻辑,也就是说在“切点”出链接程序后,通过通知来执行切面。
-
Weaving织入:是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
-
proxy代理类
-
Aspect切面:“切面”是一段程序代码,这段代码将被“植入”到程序流程中,是切入点pointcut和通知advice的结合。
一个线是一个特殊的面。
一个切入点和一个通知,组成成一个特殊的面。
2.1 手动方式
2.1.1 动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法的增强
分类:基于子类的动态代理
基于接口的动态代理
目录结构如下:
2.2.2.1 编写目标对象(Target)
public interface Iproducer {
/**
* 销售产品
* @param money
*/
void saleProduct(float money);
/**
* 售后服务
* @param money
*/
void afterService(float money);
}
2.2.1.2 切面(Aspect)
public class Producer implements Iproducer {
public void saleProduct(float money) {
System.out.println("销售产品,拿到钱"+money);
}
public void afterService(float money) {
System.out.println("售后服务,拿到钱"+money);
}
}
2.2.1.3 工厂
public class Client {
public static void main(String[] args) {
//1 目标类
final Producer producer = new Producer();
//2 切面类
Iproducer proxyProducer = (Iproducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
Float money = (Float) args[0];
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(200f);
}
}
newProxyInstance:
- ClassLoader:类加载器
用于加载代理对象字节码和代理对象使用相同的类加载器。固定写法 xxx.getClass().getClassLoader(); - Class[ ]:字节码数组
用于代理对象和被代理对象有相同的方法 xxx.getClass().getInterfaces() - InvocationHandler
用于提供增强的代码
让我们如何写代理,我们一般是写一个该接口的实现类,通常是匿名实现类,但不是必须的,此接口类都是谁用谁写
2.2 Spring 编写代理
2.2.1 所需jar包
导入 spring-context.jar
aspectjweaver.jar
目录结构如下:
2.2.2 编写service层代码
public interface IAccountService {
/**
* 无返回值无参函数
*/
void saveAccount();
/**
* 无返回值有参函数
*/
void updateAccount(int cardId);
/**
* 有返回值无参函数
*/
int deleteAccount();
}
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("保存了账户");
}
public void updateAccount(int cardId) {
System.out.println("更新了账户" + cardId);
}
public int deleteAccount() {
System.out.println("删除了账户");
return 0;
}
}
2.2.3 编写utils里面的Logger类
public class Logger {
/**
* 前置通知
*/
public void beforeprintLog() {
System.out.println("前置通知Logger类中的printLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
public void afterReturningprintLog() {
System.out.println("后置通知Logger类中的printLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingprintLog() {
System.out.println("异常通知Logger类中的printLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
public void afterprintLog() {
System.out.println("最终通知Logger类中的printLog方法开始记录日志了。。。");
}
/**
* 环绕通知
* spring框架提供了一个接口:ProceedingJoinPoint
* 有一个方法proceed()。相当于明确调用切入点方法
* 接口作为环绕通知的方法参数
*/
public Object aroundprintLog(ProceedingJoinPoint pjp) {
Object returnValue=null;
try {
System.out.println("前置通知Logger类中的printLog方法开始记录日志了。。。");
Object[] args = pjp.getArgs();
returnValue=pjp.proceed(args);
System.out.println("后置通知Logger类中的printLog方法开始记录日志了。。。");
return returnValue;
} catch (Throwable throwable) {
System.out.println("异常通知Logger类中的printLog方法开始记录日志了。。。");
throwable.printStackTrace();
return returnValue;
} finally {
System.out.println("最终通知Logger类中的printLog方法开始记录日志了。。。");
}
}
2.2.4 配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把bean交给spring来管理-->
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="accountService" class="com.lby.service.impl.AccountServiceImpl">
</bean>
<!--配置logger-->
<bean id="logger" class="com.lby.utils.Logger">
</bean>
<!--使用aop:config标签表明AOP的配置-->
<aop:config>
<!--使用aop:pointcut标签表明切入点的配置
切入点表达式的写法:
关键字:execution(表达式)
表达式:访问修饰符 返回值 包名。包名……类名.方法名(参数列表)
写法:
public void com.lby.service.impl.AccountServiceImpl.saveAccount()
全通配写法:
* *..*.*(..)
-->
<aop:pointcut id="pc" expression="execution(* com.lby.service.impl.*.*(..))"/>
<!--使用aop:aspect表明配置切面
id:给切面提供一个唯一标识
ref:指定通知类bean的id
-->
<aop:aspect id="logAdvice" ref="logger">
<!--aop:before表示前置通知
该方法在切入点方法执行之前执行
method:用于指定切入点表达式,该表达式的含义是指的是对业务层中哪些方法进行增强,指定logger类中哪个方法是前置通知
pointCut:用于指定切入点表达式
-->
<aop:before method="beforeprintLog"
pointcut-ref="pc"/>
<!--aop:after-returning表示后置通知
该方法在切入点方法正常执行后执行-->
<aop:after-returning method="afterReturningprintLog"
pointcut-ref="pc"/>
<!--aop:after-throwing表示异常通知
该方法在切入点方法产生异常后执行-->
<aop:after-throwing method="afterThrowingprintLog"
pointcut-ref="pc"/>
<!--aop:after表示最终通知
该方法无论切入点方法是否正常执行都会在后面执行-->
<aop:after method="afterprintLog"
pointcut-ref="pc"/>
<!--aop:around表示环绕通知-->
<!--<aop:around method="aroundprintLog"
pointcut-ref="pc"/>-->
</aop:aspect>
</aop:config>
</beans>
2.2.5 测试类
public class AopTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
IAccountService as = ac.getBean("accountService", IAccountService.class);
as.saveAccount();
}
}
2.3 spring通过注解实现AOP
2.3.1 编写service层代码
与上例相同
2.3.2 编写utils里面的Logger类
package com.lby.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component("logger")
@Aspect
public class Logger {
//切入点 切入点表达式
@Pointcut("execution(* com.lby.service.impl.*.*(..))")
public void pc(){}
/**
* 前置通知
*/
@Before("pc()")
public void beforeprintLog() {
System.out.println("前置通知Logger类中的printLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
@AfterReturning("pc()")
public void afterReturningprintLog() {
System.out.println("后置通知Logger类中的printLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
@AfterThrowing("pc()")
public void afterThrowingprintLog() {
System.out.println("异常通知Logger类中的printLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
@After("pc()")
public void afterprintLog() {
System.out.println("最终通知Logger类中的printLog方法开始记录日志了。。。");
}
/**
* 环绕通知
*/
//@Around("pc()")
public Object aroundprintLog(ProceedingJoinPoint pjp) {
Object returnValue = null;
try {
System.out.println("前置通知Logger类中的printLog方法开始记录日志了。。。");
Object[] args = pjp.getArgs();
returnValue = pjp.proceed(args);
System.out.println("后置通知Logger类中的printLog方法开始记录日志了。。。");
return returnValue;
} catch (Throwable throwable) {
System.out.println("异常通知Logger类中的printLog方法开始记录日志了。。。");
throwable.printStackTrace();
return returnValue;
} finally {
System.out.println("最终通知Logger类中的printLog方法开始记录日志了。。。");
}
}
}
2.3.3 配置ApplicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置要扫描的包-->
<context:component-scan base-package="com.lby"/>
spring开启注解AOP的支持
<aop:aspectj-autoproxy/>
</beans>
2.3.4测试类
和上例相同
2.3.5 运行结果
3.1 Spring中的JdbcTemplate
3.1.1 导入所需jar
spring-jdbc.jar
spring-tx.jar
spring-context.jar
作用:用于对数据库进行交互,实现对表的CRUD的操作
根据你所使用的数据库和数据库连接池,添加相应的驱动包
3.1.2 配置数据源
a. 配置DBCP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 创建模板 ,需要注入数据源-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置dao -->
<bean id="bankDao" class="com.lby.dao.impl.BankDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!-- 配置dataSource-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
<property name="url" value="jdbc:sqlserver://localhost:1433;DatabaseName=userinfo"/>
<property name="username" value="user"/>
<property name="password" value="123"/>
</bean>
</beans>
b.配置C3P0
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
<property name="url" value="jdbc:sqlserver://localhost:1433;DatabaseName=userinfo"/>
<property name="username" value="widen"/>
<property name="password" value="123456"/>
</bean>
b.spring内置数据源
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
<property name="url" value="jdbc:sqlserver://localhost:1433;DatabaseName=userinfo"/>
<property name="username" value="widen"/>
<property name="password" value="123456"/>
</bean>