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日志并进行多维度分析。
目录
相关文章
|
21天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
21天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
12天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
557 7
|
9天前
|
XML 前端开发 Java
控制spring框架注解介绍
控制spring框架注解介绍
|
9天前
|
存储 NoSQL Java
Spring Session框架
Spring Session 是一个用于在分布式环境中管理会话的框架,旨在解决传统基于 Servlet 容器的会话管理在集群和云环境中的局限性。它通过将用户会话数据存储在外部介质(如数据库或 Redis)中,实现了会话数据的跨服务器共享,提高了应用的可扩展性和性能。Spring Session 提供了无缝集成 Spring 框架的 API,支持会话过期策略、并发控制等功能,使开发者能够轻松实现高可用的会话管理。
Spring Session框架
|
17天前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
27 2
|
16天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
|
26天前
|
运维 NoSQL Java
SpringBoot接入轻量级分布式日志框架GrayLog技术分享
在当今的软件开发环境中,日志管理扮演着至关重要的角色,尤其是在微服务架构下,分布式日志的统一收集、分析和展示成为了开发者和运维人员必须面对的问题。GrayLog作为一个轻量级的分布式日志框架,以其简洁、高效和易部署的特性,逐渐受到广大开发者的青睐。本文将详细介绍如何在SpringBoot项目中接入GrayLog,以实现日志的集中管理和分析。
104 1
|
1月前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
58 2
|
1月前
|
Cloud Native 安全 Java
Micronaut对决Spring Boot:谁是微服务领域的王者?揭秘两者优劣,选对框架至关重要!
【9月更文挑战第5天】近年来,微服务架构备受关注,Micronaut和Spring Boot成为热门选择。Micronaut由OCI开发,基于注解的依赖注入,内置多种特性,轻量级且启动迅速;Spring Boot则简化了Spring应用开发,拥有丰富的生态支持。选择框架需考虑项目需求、团队经验、性能要求及社区支持等因素。希望本文能帮助您选择合适的微服务框架,助力您的软件开发项目取得成功!
99 2
下一篇
无影云桌面