JAVAEE框架整合技术之Spring02-AOP面向切面编程技术

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: JAVAEE框架整合技术之Spring02-AOP面向切面编程技术

Spring新注解

Spring5.0之后的注解称为新注解

使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置

注解 说明
@Configuration 表示当前类是一个配置类,用于代替配置文件,相当于applicationContext.xml
@Bean 作用于方法上,用于将方法的返回值存入spring容器中
@ComponentScan 用于指定扫描包路径
Value:用于指定路径 数组basePackages:和value一样
相当于 <context:component-scan base-package=“”/>

@Configuration

spring-new-annotation

/**
 *   @Configuration 相当于 applicationContext.xml配置文件
 *   @ComponentScan 相当于 <context:component-scan base-package="cn.yanqi"/>
 *   @Bean("person")相当于 <bean id="person" class="cn.yanqi.pojo.person"/>
 */
@Configuration
@ComponentScan(basePackages = "cn.yanqi")
public class SpringConfiguration {
    @Bean("person")
    public Person person(){
        return new Person();
    }
}
@Data
public class Person {
    @Value("11")
    private int id;
    @Value("jack")
    private String name;
    @Value("男")
    private String sex;
public class UserServiceTest {    
    @Test
    public void login() {
        //注意所使用对象:new AnnotationConfigApplicationContext(SpringConfiguration.class);
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        Person person = applicationContext.getBean("person", Person.class);
        System.out.println(person);
    }
}

@Bean

@Bean注入对象

/**
 * @Auther: yanqi
 * @Desc    @Configuration 相当于 applicationContext.xml配置文件
 *          @ComponentScan 相当于 <context:component-scan base-package="cn.yanqi"/>
 *          @Bean("person") 相当于 <bean id="person" class="cn.yanqi.pojo.person"/>
 */
@Configuration
@ComponentScan(basePackages = "cn.yanqi")
public class SpringConfiguration {
    @Bean("userService")
    public UserService userService(){
        //注入dao层对象
        return new UserServiceImpl(userDao());
    }
    @Bean("userDao")
    public UserDao userDao(){
        return new UserDaoImpl();
    }
}
public class UserServiceImpl implements UserService {
    private UserDao userDao;
    //提供UserServiceImpl有参构造
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void login() {
        System.out.println("业务层 登录!!!");
        userDao.login();
    }
}
/**
 * @Auther: yanqi
 * @Date: 18:35
 * @Desc
 */
public class UserDaoImpl implements UserDao {
    @Override
    public void login() {
        System.out.println("dao层登录成功!!!");
    }
}
@Test
    public void login() {
        //注意所使用对象:new AnnotationConfigApplicationContext(SpringConfiguration.class);
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }

@Component

纯注解开发

/**
 * 
 *          @Configuration 相当于 applicationContext.xml配置文件
 *          @ComponentScan 相当于 <context:component-scan base-package="cn.yanqi"/>
 *          @Bean("person")相当于 <bean id="person" class="cn.yanqi.pojo.person"/>
 */
@Configuration
@ComponentScan(basePackages = "cn.yanqi")
public class SpringConfiguration {
}
@Service("userService")
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    public void login() {
        System.out.println("业务层 登录!!!");
        userDao.login();
    }
}
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void login() {
        System.out.println("dao层登录成功!!!");
    }
}
public class UserServiceTest {
    @Test
    public void login() {
        //注意所使用对象:new AnnotationConfigApplicationContext(SpringConfiguration.class);
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        Person person = applicationContext.getBean("person", Person.class);
        System.out.println(person);
    }
}

@PropertySource

@PropertySouce是spring3.1开始引入的基于java config的注解。

通过@PropertySource注解将properties配置文件中的值存储到Spring的 Environment中,Environment接口提供方法去读取配置文件中的值,参数是properties文件中定义的key值。

@Configuration
@PropertySource("classpath:jdbc.properties")
public class PropertiesWithJavaConfig {
   @Value(${jdbc.driver})
   private String driver;
   @Value(${jdbc.url})
   private String url;
   @Value(${jdbc.username})
   private String username;
   @Value(${jdbc.password})
   private String password;
   //要想使用@Value 用${}占位符注入属性,这个bean是必须的,这个就是占位bean,另一种方式是不用value直接用Envirment变量直接getProperty('key')  
   @Bean
   public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
      return new PropertySourcesPlaceholderConfigurer();
   }
}

动态代理

什么是代理

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。


代理的核心角色

  • 抽象角色(接口类)

定义代理角色和真实角色公共对外的方法

  • 真实角色(实现类)

实现抽象角色,定义真实角色所要实现的业务逻辑,让代理角色调用

  • 真实角色-关注的是真正的业务逻辑
  • 代理角色(代理实现的类,最终使用的对象)

实现抽象角色,是真实角色的代理,通过真实角色 的业务逻辑方法来实现抽象方法,并可以附加 自己的操作

  • 将统一的流程控制放到代理角色中处理

代理的应用场景

  • 可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强
    这样我们就屏蔽了对真实角色的直接访问
  • Spring的AOP机制就是采用动态代理的机制来实现切面编程

代理的分类

  • 静态代理

需要我们手动定义静态代理类对象

  1. 目标角色固定
  2. 在应用程序执行前就得到目标角色
  3. 代理对象会增强目标对象的行为
  4. 有可能存在多个代理,引起"类爆炸"(缺点)
  • 动态代理
  • JDK 代理 : 基于接口的动态代理技术
  • cglib 代理:基于父类的动态代理技术

静态代理代码实现

  • 定义接口
package cn.yanqi.proxy;
// 抽象角色
public interface Star {
    /**      唱歌     */
    public void sing();
    /**   订票     */
    public void tickets();
    /** 签合同     */
    public void contract();
}
  • 真实对象
// 真实对象
public class RealStar implements Star {
    public void sing() {
        System.out.println("RealStar.sing.....");
    }
    public void tickets() {
        System.out.println("RealStar.tickets.....");
    }
    public void contract() {
        System.out.println("RealStar.contract.....");
    }
}
  • 代理对象
package cn.yanqi.proxy;
//代理对象
public class ProxyStar implements Star {
    private RealStar realStar;
    public ProxyStar(RealStar realStar) {
        this.realStar = realStar;
    }
    public void sing() {
        // 调用真实对象的唱歌功能
        realStar.sing();
    }
    public void tickets() {
        System.out.println("ProxyStar.tickets....");
    }
    public void contract() {
        System.out.println("ProxyStar.contract....");
    }
}
  • 测试
public class Test {
    public static void main(String[] args) {        
        RealStar realStar = new RealStar();
        ProxyStar proxyStar = new ProxyStar(realStar);
        proxyStar.tickets();
        proxyStar.contract();
        proxyStar.sing();
    }
}

静态代理对于代理的角色是固定的,如 dao 层有20个 dao 类,如果要对方法的访问权限进行代理,此时需要创建20个静态代理角色,引起类爆炸,无法满足生产上的需要,于是就催生了动态代理的思想。

动态代理代码实现

动态代理的实现有两种,JDK和CGLIB,在这里我们以JDK的方式来演示动态代理

创建出来的代理都是java.lang.reflect.Proxy的子类

  • 定义Star接口
public interface Star {
    /**  唱歌     */
    public String sing(double money);
    /** 订票     */
    public void tickets();
    /** 签合同   */
    public void contract();
}
  • 定义歌手类
public class RealStar implements Star {    
    @Override
    public String sing(double money) {
        System.out.println("真实对象收到:" + money + "元, 准备开始唱歌!");
        return "代理对象说,马上开始唱歌了!";
    }
    @Override
    public void tickets() {
        System.out.println("RealStar.tickets.....");
    }
    @Override
    public void contract() {
        System.out.println("RealStar.contract.....");
    }
}
  • 创建代理类
package cn.yanqi.proxy;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import java.lang.reflect.Method;
public class JDKProxy {
    public static void main(String[] args) {
        //真实对象
        final RealStar realStar = new RealStar();
        /**
         * 创建代理对象
         *  参数一: 真实对象getClass().getClassLoader()
         *  参数二: 真实对象getClass().getInterfaces()
         *  参数三: 处理器new InvocationHandler
         */
        Star proxyObject = (Star) Proxy.newProxyInstance(realStar.getClass().getClassLoader(),
                realStar.getClass().getInterfaces(),new InvocationHandler() {
            /**
             * @param proxy  代理对象 一般不用
             * @param method 代理对象调用的方法,被封装为Method对象
             * @param args   代理对象调用方法时,传递的参数
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 对sing方法进行增强
                if (method.getName().equals("sing")) {
                    // 对象参数进行增强
                    Double money = (Double) args[0];
                    // 代理收取代理费
                    money = money - 5000;
                    Object obj = method.invoke(realStar, money);
                    // 对象返回值进行增强
                    return obj + "开始之前,给大家一个小礼物!";
                } else {
                    // 其他方法,还是执行原有的逻辑
                    Object obj = method.invoke(realStar, args);
                    return obj;
                }
            }
        });
         //调用真实对象的sing方法
         String sing = proxyObject.sing(10000);
         System.out.println(sing);
    }
}
  • 测试

AOP概述

什么是 AOP

AOP 为 Aspect Oriented Programming 的缩写,意思为【面向切面编程】,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !

AOP 的作用及其优势

1. 在程序运行期间,在不修改源码的情况下对方法进行功能增强
2. 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
3. 减少重复代码,提高开发效率,便于后期维护

AOP 的底层实现

实际上,AOP 的底层是通过动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

AOP 的动态代理技术

  • JDK 代理 : 基于接口的动态代理技术
  • cglib 代理:基于父类的动态代理技术

AOP相关专业术语

  • 目标对象(target)
目标对象指将要被增强的对象,即包含主业务逻辑的类对象。
  • 连接点(JoinPoint)
程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
连接点由两个信息确定:
   ==方法(表示程序执行点,即在哪个目标方法)==
   ==相对点(表示方位,即目标方法的什么位置,比如调用前,后等)==
简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。
  • 代理对象(Proxy)
AOP中会通过代理的方式,对目标对象生成一个代理对象,代理对象中会加入需要增强功能,通过代理对象来间接的方式目标对象,起到增强目标对象的效果。
  • 通知/增强(Advice)
需要在目标对象中实现增强的功能
  • 切入点(Pointcut)
用来指定需要将通知使用到哪些地方,比如需要用在哪些类的哪些方法上,切入点就是做这个配置的。
  • 切面(Aspect)
通知(Advice)和切入点(Pointcut)的组合。切面来定义在哪些地方(Pointcut)执行什么操作(Advice)。
  • 织入(Weaving)
把Advice加到Target上,被创建出代理对象的过程

切入点表达式

切点表达式语法

bean(bean Id/bean name) 
execution(* cn.yanqi.spring.CustomerServiceImpl.*(..)) 
execution(* cn.yanqi.spring..*.*(..)) 
  • 访问修饰符可以省略
  • 返回值类型、包名、类名、方法名可以使用星号*代替,代表任意
  • 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
  • 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表

Aop 基于_xml配置

添加依赖

  • 添加aop依赖
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.9.RELEASE</version>
</dependency>
<!--aop依赖包在context依赖中已存在-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.2.9.RELEASE</version>
</dependency>
  • 引入aop约束
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> 
</beans>

实现步骤

第一步:目标(确定)

第二部:通知/增强(编写)

第三步:配置切面 (包括切入点和切面的结合)对哪些方法进行怎么的增强


目标类

/**
 * @Auther: yanqi
 * @Desc 目标类
 */
public interface CustomerService {
    public void save();
    public int findCount();
}
package cn.yanqi.service;
/**
 * @Auther: yanqi
 * @Desc 目标类
 */
public class CustomerServiceImpl implements CustomerService {
    public void save() {
        System.out.println("业务层:【客户保存了】-----------");
    }
    public int findCount() {
        System.out.println("业务层:【客户查询了】-----------");
        return 100;
    }
}
/**
 * @Auther: yanqi
 * @Desc 目标类
 */
public class ProductService {
    public void save(){
        System.out.println("业务层:【商品保存了】-----------");
    }
    public int findCount() {
        System.out.println("业务层:【查询商品了】-----------");
        return 200;
    }
}

增强类

/**
 * @Auther: yanqi
 * @Desc 配置增强类
 */
public class MyAdvice {
    public void before(){
        System.out.println("前置增强了-----------");
    }
    public void after(){
        System.out.println("后置增强了-----------");
    }
}

配置切面及切入点

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--目标类-->
    <bean id="customerService" class="cn.yanqi.service.CustomerServiceImpl"/>
    <bean id="productService" class="cn.yanqi.service.ProductService"/>
    <!--增强类-->
    <bean id="myAdvice" class="cn.yanqi.advice.MyAdvice"/>
    <!--配置切面和切入点-->
    <aop:config>
        <!--配置切入点:其实就是要拦截到哪些方法,以service结尾的bean都被拦截,增强-->
        <aop:pointcut id="MyPointcut" expression="bean(*Service)"/>
        <!--配置切面: 关联切入点和切面,要对哪些方法进行怎么的增强-->
        <aop:aspect ref="myAdvice">
            <!--
                前置通知:
                    method:  调用增强类的方法
                    pointcut-ref:关联切入点
            -->
            <aop:before method="before" pointcut-ref="MyPointcut"/>            
            <!--后置通知-->
            <aop:after-returning method="after" pointcut-ref="MyPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
  • 测试
/**
 * @Auther: yanqi
 * @Desc  在不修改的原来代码情况下,对其目标对象进行功能增强
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations =  "classpath:applicationContext.xml")
public class CustomerServiceTest {
    //注入业务层
    @Autowired
    private CustomerService customerService;
    @Autowired
    private ProductService productService;
    @Test
    public void test() {
        this.customerService.findCount();
        this.customerService.save();
        this.productService.findCount();
        this.productService.save();
    }
}

分析各种通知应用

通知类型

名称 标签 说明 场景
前置通知 before 被增强方法执行之前执行 权限控制、日志记录等
后置通知 afterReturning 被增强方法正常执行完毕后执行(执行的过程中无异常) 提交事务/统计分析结果等
异常通知 afterThrowing 被增强方法出现异常时执行 回滚事务/记录异常的日志信息等
最终通知 after 被增强方法无论是否有异常,最终都要执行的一种操作 释放资源
环绕通知 around 可以自定义在被增强方法的什么时机执行(返回Object,参数 processedingJoinPoint) 缓存、性能日志、权限、事务管理

前置通知

package cn.yanqi.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
 * @Dese: 增强类
 */
public class MyAdvice {
    /**
     * 前置通知
     * 参数:
     *      joinPoint 连接点:就是可以拦截到的方法(方法和目标的包装类型)
     * 需求:
     *      权限控制(权限不足,抛出异常),记录调用方法的日志
     */
    public void before(JoinPoint joinPoint){
        System.out.println("前置增强了-----------");
        //joinPoint.getTarget().getClass().getName() 获取要执行的所在的类 包名+类名
        System.out.println("增强的对象:"+joinPoint.getTarget().getClass().getName());
        //joinPoint.getSignature().getName() 获取方法名
        if("findCount".equals(joinPoint.getSignature().getName())){
            throw new RuntimeException("当前用户没有权限");
        }
    }
}
<!--
前置通知:
method:  调用增强类的方法
pointcut-ref:关联切入点
--> 
<aop:before method="before" pointcut-ref="MyPointcut"/>

后置通知

/**
     * 后置通知: 会在方法执行后进行拦截增强
     * 需求:与业务相关,如网上营业厅查询余额自动下发短信
     * 参数1:连接点
     * 参数2:Object 接受返回值
     */
    public void AfterReturning(JoinPoint joinPoint ,Object returnValue){
        System.out.println("后置通知--下发短信的方法是: "+joinPoint.getSignature().getName());
        System.out.println("短信内容:尊敬的用户您的余额为:"+ returnValue);
    }
<!--后置通知:returning 返回的内容-->
<aop:after-returning method="AfterReturning" pointcut-ref="MyPointcut" returning="returnValue"/>

环绕通知

/**
     * 环绕通知: 在方法前后拦截增强
     * 需求: 日志,缓存,权限,性能监控,事务管理
     * 参数:ProceedingJoinPoint 正在执行的连接点
     * 必须抛出一个异常Throwable
     */
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("事务开启了");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("事务关闭了");
        return proceed;
    }
<!--环绕通知-->
<aop:around method="around" pointcut-ref="MyPointcut"/>

抛出通知

/**
     * 抛出通知: 对目标对象发生异常下进行增强,有异常就执行,没有就不执行
     * 作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
     * 应用场景:处理异常(一般不可预知),记录日志
     * 参数1: 连接点
     * 参数2: 异常类型
     */
    public void AfterThrowing(JoinPoint joinPoint, Throwable ex){
        System.out.println(
                "管理员您好:发生异常的类是:"+joinPoint.getTarget().getClass().getSimpleName()
                +" ,发生异常的方法是:"+joinPoint.getSignature().getName()
                +", 异常信息为:"+ex.getMessage());
    }
<!--抛出通知-->
<aop:after-throwing method="AfterThrowing" pointcut-ref="MyPointcut" throwing="ex"/>
public class CustomerServiceImpl implements CustomerService {
    public void save() {
        int a = 1/0; //手动给出异常
        System.out.println("业务层:【客户保存了】-----------");
    }
    public int findCount() {
        System.out.println("业务层:【客户查询了】-----------");
        return 100;
    }
}

最终通知

/**
     * 最终通知: 不管是否异常都会增强
     * 作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)
     * 应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
     */
    public void after(JoinPoint joinPoint){
        System.out.println("释放资源:"+joinPoint.getSignature().getName());
    }
<!--最终通知-->
<aop:after method="after" pointcut-ref="MyPointcut"/>

Aop基于_注解配置

注解添加的依赖和xml的依赖包是一样的

spring-aop-annocation

目标对象

public interface CustomerService {
    public void save();
    public int findCount();
}
//目标类
@Service("customerService")
public class CustomerServiceImpl implements CustomerService {
    @Override
    public void save() {
        //手动制造异常
        int a = 1/0;
        System.out.println("业务层:【客户保存了】-----------");
    }
    @Override
    public int findCount() {
        System.out.println("业务层:【客户查询了】-----------");
        return 100;
    }
}
//目标类
@Service("productService")
public class ProductService {
    public void save(){
        System.out.println("业务层:【商品保存了】-----------");
    }
    public int findCount() {
        System.out.println("业务层:【查询商品了】-----------");
        return 200;
    }
}
  • applicationContext.xml
<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
   http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启注解扫描-->
    <context:component-scan base-package="cn.yanqi"/>
    <!--开启AspectJ 注解自动代理机制
        扫描含有@Aspect的bean
     -->
    <aop:aspectj-autoproxy/>
</beans>

增强类

/**
 * @Auther: yanqi
 * @Date:
 * @Dese: 增强类
 */
@Component("myAdvice")
@Aspect //相当于<aop:aspect ref="myAdvice">
public class MyAdvice {
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-aop.xml")
public class SpringAopController {
    //注入业务层
    @Autowired
    private CustomerService customerService;
    @Autowired
    private ProductService productService;
    @Test
    public void testSpringAop(){
        //基于接口
        this.customerService.save();
        this.customerService.findCount();
        //基于类
        this.productService.save();
        this.productService.findCount();
    }
}

编写通知,配置切面

  • 注解切点表达式

注解切点表达式的写法与xml版本的切点表达式写法是一样的,在这里主要演示如何使用注解抽取切点表达式

@Component("myAdvice")
@Aspect //相当于<aop:aspect ref="myAdvice">
public class MyAdvice {
    /**
     * 前置通知
     * 参数:
     *      joinPoint 连接点:就是可以拦截到的方法(方法和目标的包装类型)
     * 需求:
     *      权限控制(权限不足,抛出异常),记录调用方法的日志
     */
    @Before("bean(*Service)")
    public void before(JoinPoint joinPoint){
        System.out.println("前置增强了-----------");
        //joinPoint.getTarget().getClass().getName() 获取要执行的所在的类 包名+类名
        System.out.println("增强的对象:"+joinPoint.getTarget().getClass().getName());
    }
    /**
     * 后置通知: 会在方法执行后进行拦截增强
     * 需求:与业务相关,如网上营业厅查询余额自动下发短信
     * 参数1:连接点
     * 参数2:Object 接受返回值
     */
    @AfterReturning(value = "target(cn.yanqi.service.CustomerServiceImpl)", returning = "returnValue")
    public void AfterReturning(JoinPoint joinPoint ,Object returnValue){
        System.out.println("后置通知--下发短信的方法是: "+joinPoint.getSignature().getName());
        System.out.println("短信内容:尊敬的用户您的余额为:"+ returnValue);
    }
    /**
     * 环绕通知: 在方法前后拦截增强
     * 需求: 日志,缓存,权限,性能监控,事务管理
     * 参数:ProceedingJoinPoint 正在执行的连接点
     * 必须抛出一个异常Throwable
     */
    @Around(value = "execution(* cn.yanqi..*.*(..))")
    // @Around(value = "execution(* cn..*.save(..))")
    // @Around(value = "execution(* cn..*.find*(..))")
    // @Around(value = "execution(* cn.yanqi.service.CustomerService+.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("事务开启了");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("事务关闭了");
        return proceed;
    }
    /**
     * 抛出通知: 对目标对象发生异常下进行增强,有异常就执行,没有就不执行
     * 作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
     * 应用场景:处理异常(一般不可预知),记录日志
     * 参数1: 连接点
     * 参数2: 异常类型
     */
    @AfterThrowing(value = "within(cn..*)",throwing = "ex") //增强cn下的及其子包下面的所有bean的类型的类
    public void AfterThrowing(JoinPoint joinPoint, Throwable ex){
        System.out.println(
                "管理员您好:发生异常的类是:"+joinPoint.getTarget().getClass().getSimpleName()
                        +" ,发生异常的方法是:"+joinPoint.getSignature().getName()
                        +", 异常信息为:"+ex.getMessage());
    }
    /**
     * 最终通知: 不管是否异常都会增强
     * 作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)
     * 应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
     */
    @After("bean(*ce)")
    public void after(JoinPoint joinPoint){
        System.out.println("释放资源:"+joinPoint.getSignature().getName());
    }
}

抽取切入点

问题:如果直接在通知注解中写切入点表达式,会发生重复编写,后期不便于维护
解决: 统一配置切入点表达式 
在实际开发中,切入点都是单独/统一定义维护的,如:
  使用xml定义切入点<aop:pointcut>
  使用注解单独定义切入点@Pointcut
语法要求:
  切入点方法:必须是 private void 无参数方法,方法名为切点名
@Component("myAdvice")
@Aspect //相当于<aop:aspect ref="myAdvice">
public class MyAdvice {
    /**
     * 配置切入点统一管理,统一维护
     */
    @Pointcut("bean(*Service)")
    private void myPointcut(){}
    @Pointcut("bean(customerService)")
    private void myPointcut2(){}
    /**
     * 前置通知
     * 参数:
     *      joinPoint 连接点:就是可以拦截到的方法(方法和目标的包装类型)
     * 需求:
     *      权限控制(权限不足,抛出异常),记录调用方法的日志
     */
    @Before(value = "myPointcut() || myPointcut2() || bean(*Service)")
    public void before(JoinPoint joinPoint){
        System.out.println("前置增强了-----------");
        //joinPoint.getTarget().getClass().getName() 获取要执行的所在的类 包名+类名
        System.out.println("增强的对象:"+joinPoint.getTarget().getClass().getName());
    }
}
oid after(JoinPoint joinPoint){
System.out.println(“释放资源:”+joinPoint.getSignature().getName());
}
}
## 抽取切入点 
```markdown
问题:如果直接在通知注解中写切入点表达式,会发生重复编写,后期不便于维护
解决: 统一配置切入点表达式 
在实际开发中,切入点都是单独/统一定义维护的,如:
  使用xml定义切入点<aop:pointcut>
  使用注解单独定义切入点@Pointcut
语法要求:
  切入点方法:必须是 private void 无参数方法,方法名为切点名
@Component("myAdvice")
@Aspect //相当于<aop:aspect ref="myAdvice">
public class MyAdvice {
    /**
     * 配置切入点统一管理,统一维护
     */
    @Pointcut("bean(*Service)")
    private void myPointcut(){}
    @Pointcut("bean(customerService)")
    private void myPointcut2(){}
    /**
     * 前置通知
     * 参数:
     *      joinPoint 连接点:就是可以拦截到的方法(方法和目标的包装类型)
     * 需求:
     *      权限控制(权限不足,抛出异常),记录调用方法的日志
     */
    @Before(value = "myPointcut() || myPointcut2() || bean(*Service)")
    public void before(JoinPoint joinPoint){
        System.out.println("前置增强了-----------");
        //joinPoint.getTarget().getClass().getName() 获取要执行的所在的类 包名+类名
        System.out.println("增强的对象:"+joinPoint.getTarget().getClass().getName());
    }
}


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
3月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
81 1
|
16天前
|
Java Spring
一键注入 Spring 成员变量,顺序编程
介绍了一款针对Spring框架开发的插件,旨在解决开发中频繁滚动查找成员变量注入位置的问题。通过一键操作(如Ctrl+1),该插件可自动在类顶部添加`@Autowired`注解及其成员变量声明,同时保持光标位置不变,有效提升开发效率和代码编写流畅度。适用于IntelliJ IDEA 2023及以上版本。
一键注入 Spring 成员变量,顺序编程
|
1月前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
145 1
什么是AOP面向切面编程?怎么简单理解?
|
1月前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
68 5
|
2月前
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
180 4
|
2月前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
34 1
|
3月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
49 1
|
3月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
【9月更文挑战第9天】AOP(面向切面编程)通过分离横切关注点提高模块化程度,如日志记录、事务管理等。Micronaut AOP基于动态代理机制,在应用启动时为带有特定注解的类生成代理对象,实现在运行时拦截方法调用并执行额外逻辑。通过简单示例展示了如何在不修改 `CalculatorService` 类的情况下记录 `add` 方法的参数和结果,仅需添加 `@Loggable` 注解即可。这不仅提高了代码的可维护性和可扩展性,还降低了引入新错误的风险。
54 13
|
2月前
|
Java 容器
AOP面向切面编程
AOP面向切面编程
48 0
|
2月前
|
XML Java 数据库连接
【2020Spring编程实战笔记】Spring开发分享~(上)
【2020Spring编程实战笔记】Spring开发分享~
59 0