Spring AOP 面向切面编程
- Spring AOP - Aspect Oriented Programming面向切面编程
- AOP的做法是将通用、与业务无关的功能抽象封装为切面类
- 切面可配置在目标方法的执行前、后运行,真正做到即插即用
在不修改源码的情况下对程序进行扩展
举例
在原方法执行之前(获取其他时机)添加另外执行事件
public class EmployeeDao {
public void insert(){
System.out.println("新增员工");
}
}
public class UserDao {
public void insert(){
System.out.println("新增用户");
}
}
public class EmployeeService {
private EmployeeDao employeeDao;
public void entry(){
System.out.println("执行员工入职业务逻辑");
employeeDao.insert();
}
public EmployeeService() {
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
}
public class UserService {
private UserDao userDao;
public UserService() {
}
public void createUser() {
System.out.println("执行创建用户业务逻辑");
userDao.insert();
}
public String generateRandomPassword(String type, Integer length) {
System.out.println("按" + type + "方式生成" + length + "位随机密码");
return "zdaa12";
}
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
在POP.xml添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--aspectjweaver是Spring AOP的底层依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
创建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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/>
<bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"/>
<bean id="userService" class="com.imooc.spring.aop.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService">
<property name="employeeDao" ref="employeeDao"/>
</bean>
<!--AOP配置-->
<bean id="methodAspect" class="com.imooc.spring.aop.aspect.MethodAspect"/>
<aop:config>
<!-- expression表达式↓↓↓↓↓↓ -->
<!-- PointCut切点,使用execution表达式描述切面的作用范围-->
<!--execution(public * com.imooc..*.**(..))说明切面作用在com.imooc包下的所有类的所哟方法-->
<!--<aop:pointcut id="pointcut" expression="execution(public * com.imooc..*.*(..))"/>-->
<!--只对所有Service类有效-->
<!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(..))"/>-->
<!--只对返回值为String方法有效-->
<!--<aop:pointcut id="pointcut" expression="execution(String com.imooc..*Service.*(..))"/>-->
<!--对方法名进行约束-->
<!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.create*(..))"/>-->
<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(*,*))"/>
<!--定义切面类-->
<aop:aspect ref="methodAspect">
<!--before通知,代表在目标方法运行前先执行methodAspect.printExecutionTime()-->
<!--brfore 通知 method 执行的方法 pointcut-ref 作用范围-定义的切点-->
<aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
<!--返回后通知-->
<aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
<!-- 执行后通知 -->
<aop:after method="doAfter" pointcut-ref="pointcut"/>
<!--异常通知-->
<aop:after-throwing method="doAfterThrowing" throwing="th" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
public class MethodAspect {
//切面方法,用于扩展额外的功能
//JoinPoint 连接点 通过连接点可以获取目标类/方法的信息
public void printExecutionTime(JoinPoint joinPoint) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
String now = sdf.format(new Date());
String className = joinPoint.getTarget().getClass().getName();// 获取目标类的名称
String methodName = joinPoint.getSignature().getName();// 获取目标方法名称
System.out.println("------>" + now + ":" + className + "." + methodName);
Object[] args = joinPoint.getArgs();
System.out.println("------>参数个数:" + args.length);
for (Object arg :
args) {
System.out.println("------>参数:" + arg);
}
}
public void doAfterReturning(JoinPoint joinPoint, Object ret) {
System.out.println("<------返回后通知" + ret);
}
public void doAfter(JoinPoint joinPoint) {
System.out.println("<------触发后置通知");
}
public void doAfterThrowing(JoinPoint joinPoint, Throwable th) {
System.out.println("<------异常通知:" + th.getMessage());
}
}
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"classpath:applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.createUser();
userService.generateRandomPassword("MD5", 16);
}
}
AOP 关键概念
Spring AOP与AspectJ的关系
- Eclipse AspectJ,一种基于Java平台的面向切面编程的语言
- Spring AOP使用AspectJWeaver实现类与方法匹配
- Spring AOP利用代理模式实现对象运行时功能扩展
AOP配置过程 - 依赖AspectJ
- 实现切面类/方法
- 配置Aspect Bean
- 定义PointCut(切点)
- 配置Advice
连接点参数
上边那个代码都有。
execution表达式
上边配置文件有案例。
五种通知类型
执行顺序与我们在配置文件中的顺序一致
特殊的“通知”-引介增强 (了解一下就行了,平常不用)
- 引介增强(IntroductionInterceptor)是对类的增强,而非方法
- 引介增强允许在运行时为目标类增加新属性或方法
- 引介增强允许在运行时改变类的行为,让类随运行环境动态变更
上边源码也有使用案例。
环绕通知
利用AOP进行方法性能筛查(将执行时间超过1s的方法列出来)
主要就是这个类
public class MethodChecker {
//ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
public Object check(ProceedingJoinPoint pjp) throws Throwable {
try {
long startTime = new Date().getTime();
Object ret = pjp.proceed();//执行目标方法
long endTime = new Date().getTime();
long duration = endTime - startTime;//执行时长
if (duration >= 1000) {
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
String now = sdf.format(new Date());
System.out.println("====" + now + ":" + className + "." + methodName + "(" + duration + "ms)====");
}
return ret;
} catch (Throwable throwable) {
System.out.println("Exception Message:" + throwable.getMessage());
throw throwable;
}
}
}
<bean id="methodChecker" class="com.imooc.spring.aop.aspect.MethodChecker"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.imooc..*.*(..))"/>
<aop:aspect ref="methodChecker">
<!--环绕通知-->
<aop:around method="check" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"classpath:applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.createUser();
}
}
public class UserService {
private UserDao userDao;
public UserService() {}
public void createUser() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行创建用户业务逻辑");
userDao.insert();
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
利用注解配置Spring AOP
- 引入依赖
- 配置文件增加注解相关设置
<context:component-scan base-package="com.imooc"/>
<!--启用AOP注解模式-->
<aop:aspectj-autoproxy/>
@Repository
public class EmployeeDao {
public void insert(){
System.out.println("新增员工");
}
}
// 用于持久化
@Repository
public class UserDao {
public void insert(){
System.out.println("新增用户");
}
}
@Service
public class EmployeeService {
@Resource
private EmployeeDao employeeDao;
public void entry(){
System.out.println("执行员工入职业务逻辑");
employeeDao.insert();
}
public EmployeeService() {
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
}
public interface IUserService {
public void createUser();
}
@Service
public class UserService implements IUserService{
@Resource
private UserDao userDao;
public UserService() {
}
public void createUser() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行创建用户业务逻辑");
userDao.insert();
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
- 增加切面类
@Component // 标记当前类为组件
@Aspect // 说明当前类是切面类
public class MethodChecker {
// 环绕通知 参数为PointCut切点表达式
@Around("execution(* com.imooc..*Service.*(..))")
public Object check(ProceedingJoinPoint pjp) throws Throwable {
try {
long startTime = new Date().getTime();
Object ret = pjp.proceed();//执行目标方法
long endTime = new Date().getTime();
long duration = endTime - startTime;//执行时长
if (duration >= 1000) {
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
String now = sdf.format(new Date());
System.out.println("====" + now + ":" + className + "." + methodName + "(" + duration + "ms)====");
}
return ret;
} catch (Throwable throwable) {
System.out.println("Exception Message:" + throwable.getMessage());
throw throwable;
}
}
}
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"classpath:applicationContext.xml");
IUserService userService = context.getBean("userService", IUserService.class);
userService.createUser();
}
}
Spring AOP 实现原理
- Spring基于代理模式实现功能动态扩展,包含两种形式:
- 目标类拥有接口,通过JDK动态代理实现功能扩展
- 目标类没有接口,通过CGLib组件实现功能扩展
静态代理案例:
// 用户服务
public interface UserService {
public void createUser();
}
public class UserServiceImpl implements UserService {
@Override
public void createUser() {
System.out.println("执行创建用户业务逻辑");
}
}
代理类
//静态代理是指必须手动创建代理类的代理模式使用方式
public class UserServiceProxy implements UserService {
// 持有委托类的对象
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void createUser() {
System.out.println("======" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) + "=======");
userService.createUser();
}
}
public class Application {
public static void main(String[] args) {
UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.createUser();
}
}
- 代理模式可以嵌套
public class UserServiceProxy1 implements UserService{
private UserService userService;
public UserServiceProxy1(UserService userService) {
this.userService = userService;
}
public void createUser(){
userService.createUser();
System.out.println("======后置扩展功能======");
}
}
public class Application {
public static void main(String[] args) {
UserService userService = new UserServiceProxy1(new UserServiceProxy(new UserServiceImpl()));
userService.createUser();
}
}
动态代理案例:
/**
* InvocationHandler是JDK提供的反射类,用于在JDK动态代理中对目标方法进行增强
* InvocationHandler实现类与切面类的环绕通知类似
*/
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;//目标对象
private ProxyInvocationHandler(Object target) {
this.target = target;
}
/**
* 在invoke()方法对目标方法进行增强
*
* @param proxy 代理类对象
* @param method 目标对象方法
* @param args 目标方法实参
* @return 目标方法执行后返回值
* @throws Throwable 目标方法抛出的异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("======" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) + "=======");
Object ret = method.invoke(target, args);//调用目标方法
return ret;
}
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler(userService);
// 动态创建代理类
// 第一个参数:类加载器
// 第二个参数:当前类要实现的接口
// 第三个参数:(如何对目标方法进行扩展)刚才实例化的invocationHandler
// 返回的类型就是,传入的对象
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
invocationHandler);
userServiceProxy.createUser();
// 动态代理,必须实现接口才可以运行
EmployeeService employeeService = new EmployServiceImpl();
EmployeeService employeeServiceProxy =
(EmployeeService) Proxy.newProxyInstance(employeeService.getClass().getClassLoader(),
employeeService.getClass().getInterfaces(),
new ProxyInvocationHandler(employeeService));
employeeServiceProxy.createEmployee();
}
}
public interface EmployeeService {
public void createEmployee();
}
public class EmployServiceImpl implements EmployeeService{
@Override
public void createEmployee() {
System.out.println("执行创建员工业务逻辑");
}
}
public interface UserService {
public void createUser();
}
public class UserServiceImpl implements UserService {
@Override
public void createUser() {
System.out.println("执行创建用户业务逻辑");
}
}
CGLib实现代理类
- CGLib是运行时字节码增强技术
- Spring AOP扩展无接口类使用CGLib
- AOP会运行时生成目标继承类字节码的方式进行行为扩展
Spring AOP实现原理 两种情况
- 目标类实现了接口,Spring优先底层使用JDK动态代理来生成目标类的代理,从而实现目标的扩展
- 目标类没有实现接口,则自动使用CGLib来通过继承的方式对目标类进行扩展