2020-12-13

spring第三天学习笔记整理


一、AOP概念及相关术语

1.概念

AOP 为Aspect Oriented Programming的缩写,意为:面向切面编程。是一种通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的编程思想。

2.相关术语

1.Target
目标对象:指的是需要被增强的对象
2.Proxy
代理对象:指的是增强后产生的对象
3.JoinPoint
连接点:目标对象中的所有方法都有机会被增强,所以都可以称之为连接点
4.PointCut
切入点:指的是真正被增强的方法(核心业务方法)
5.Advice
通知:指的是增强的方法(通用业务方法)
6.Aspect
切面:切入点和通知的结合体
7.Weaving
织入:将通知和切入点结合的过程

二、AOP增强对象示例

1.实现思路

1.编写核心业务对象,也就是目标对象,里面包含需要被增强的核心业务方法
2.编写通知对象,里面包含增强的方法(通用的业务逻辑)
3.通知配置的方式告知spring,在指定时机,通过通知类中的指定的方法,去增强目标对象中的指定方法

2.示例

1.导入相关坐标
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>
2.编写通知类,在类中定义一个before的方法
public class LogAdvice{
	public void beforeLog(){
		System.out.println("方法执行前");
	}
}
3.在spring核心配置文件中配置目标对象和通知对象
4.引入AOP名称空间
5.完成AOP的织入,建立通知方法和切入点方法的关系
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
             http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="service.impl.UserServiceImpl"/>

    <bean id="logAdvice" class="advice.LogAdvice"/>

    <aop:config>
        <aop:aspect ref="logAdvice">
            <aop:before method="beforeLog" pointcut="execution(* service.impl.UserServiceImpl.add())"/>
        </aop:aspect>
    </aop:config>

</beans>

思维图:
在这里插入图片描述

三、切入点和通知

1.切入点表达式的书写方式

1.基本语法:
	execution(访问修饰符 返回值 包名.类名.方法名(参数))
2.特殊写法
	访问修饰符可以省略
	返回值可以用*代表任意返回值
	包名
		可以使用*代表任意名称的包名
		可以使用多个*代表指定层级的包
		可以使用*..代表任意名称任意层级的包
	类名可以使用*代表任意类
	方法名可以使用*代码任意方法名
	参数
		基本数据类型可以写名称,如int
		引用数据类型可以写全类名
		可以使用*代表任意数据类型参数,多个参数使用,分割
		可以使用..代表任意类型任意个数参数
3.常用写法
	通常情况下会切到业务层实现包中任意类的任意方法上,所以切入点表达式通常写法如下:
	execution(* 业务层实现包.*.*(..))
	execution(* *.*.service.impl.*.*(..)

2.切入点表达式的抽取

可以通过标签将<aop:pointcut>相同切入点表达式,抽取到一个标签中,
使用时再用pointcut-ref属性引用这个标签:
<aop:config>
    <!--抽取通用的切入点表达式-->
    <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*())"/>
    <aop:aspect ref="logAdvice">
        <!--前置通知:在切入点方法执行之前执行-->
        <aop:before method="beforeLog" pointcut-ref="pt1"/>
    </aop:aspect>
</aop:config>

3.通知类型

在springAOP中一共存在5种不同的通知类型,分别为:

1.前置通知:在切入点方法执行之前执行,通过 <aop:before> 表示
2.后置通知:在切入点方法正常执行之后执行,通过<aop:after-returning>表示
3.异常通知:在切入点方法产生异常之后执行,通过 <aop:after-throwing> 表示
4.最终通知:无论切入点方法是否产生异常最终都会执行,通过 <aop:after> 表示
5.环绕通知:* 可以替代上述所有通知,最为特殊,通过 <aop:around>表示

前四种通知的实现方式比较类似,实现方式如下:

1.在通知类LogAdvice中添加通知方法

public class LogAdvice {
    public void beforeLog(){
        System.out.println("方法执行前");
    }

    public void afterReturningLog(){
        System.out.println("方法正常执行后");
    }

    public void afterThrowingLog(){
        System.out.println("方法执行产生异常后");
    }

    public void afterLog(){
        System.out.println("方法最终执行结束");
    }
}

2.在配置文件中配置通知方法

<aop:config>
    <aop:pointcut id="pt1" expression="execution(* *.*.service.impl.*.*())"/>
    <aop:aspect ref="logAdvice">
        <!--前置通知:在切入点方法执行之前执行-->
        <aop:before method="beforeLog" pointcut-ref="pt1"/>
        <!--后置通知:在切入点方法正常执行之后执行-->
        <aop:after-returning method="afterReturningLog" pointcut-ref="pt1" />
        <!--异常通知:在切入点方法产生异常之后执行-->
        <aop:after-throwing method="afterThrowingLog" pointcut-ref="pt1"/>
    </aop:aspect>
</aop:config>

4.通知方法的参数

前四种通知方法中都可以添加一个 JoinPont 类型的参数,用于获取切入点方法的信息。该对象有如下常见方法:

  • Object getTarget() 获取目标对象
  • Signature getSingature() 获取切入点方法的签名对象,可以通过 Signature 的 getName 方法获取切入点方法的方法名

如现在需要对日志功能进行升级,要在控制台打印“xxxx的xxx方法执行前”,“xxxx的xxx方法正常执行后“ 等日志信息。则 LogAdvice 的代码作出如下修改即可:

public class LogAdvice {
    public void beforeLog(JoinPoint joinPoint){
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println(simpleName+"的"+methodName+"方法执行前");
    }

    public void afterReturningLog(JoinPoint joinPoint){
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println(simpleName+"的"+methodName+"方法正常执行后");
    }

    public void afterThrowingLog(JoinPoint joinPoint){
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println(simpleName+"的"+methodName+"方法执行产生异常后");
    }

    public void afterLog(JoinPoint joinPoint){
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println(simpleName+"的"+methodName+"方法最终执行结束");
    }
}

5.环绕通知

由于环绕通知比较特殊,所以单独对它的概念及使用进行介绍。

环绕通知的概念:

所谓的环绕通知并不是在切入点方法执行前后调用通知方法对切入点进行增强。而是要手动在环绕通知方法中调用切入点方法。这样就可以在调用前后,产生异常后,以及最终对切入点方法进行增强,达到环绕的效果。

环绕通知方法的书写规则:

  1. 方法必须接受一个 ProceedingJoinPoint 类型的参数,代表正在执行的切入点方法对象
  2. 方法必须有一个 Object 类型的返回值,代表切入点方法执行后产生的返回值
  3. 必须在环绕通知方法中手动调用 ProceedingJoinPoint 的 proceed 方法执行切入点方法

代码实现:

  1. 编写环绕通知方法
public Object aroundLog(ProceedingJoinPoint pjp){
    String simpleName = pjp.getTarget().getClass().getSimpleName();
    Signature signature = pjp.getSignature();
    String methodName = signature.getName();
    try {
        // 前置
        System.out.println(simpleName+"的"+methodName+"方法执行前");
		// 调用切入点方法
        Object rtValue = pjp.proceed();
        // 后置
        System.out.println(simpleName+"的"+methodName+"方法正常执行后");
        return rtValue;
    } catch (Throwable throwable) {
        throwable.printStackTrace();
        // 异常
        System.out.println(simpleName+"的"+methodName+"方法执行产生异常后");
        return null;
    }finally {
        // 最终
        System.out.println(simpleName+"的"+methodName+"方法最终执行结束");
    }
}
  1. 配置环绕通知方法
<aop:config>
     <!--抽取通用的切入点表达式-->
     <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*())"/>
     <aop:aspect ref="logAdvice">
         <!--环绕通知-->
         <aop:around method="aroundLog" pointcut-ref="pt1"/>
     </aop:aspect>
</aop:config>

由此可以看出,环绕通知方法可以替代前置,后置,异常,以及最终通知。而且由于切入点方法是在环绕通知方法内部被手动调用的,因此可以增强切入点方法的参数和返回值。

四、通知中获取切入点方法的参数,返回值及异常信息

1.准备工作

1.添加OrderService接口和OrderServiceImpl实现类
public interface OrderService {
    public void add(String name, int money);

    public int findCount();

    public void update();
}
public class OrderServiceImpl implements OrderService {
    public void add(String name, int money) {
        System.out.println("添加订单..."+name+"   "+money);
    }

    public int findCount() {
        System.out.println("查询订单总数");
        return 10;
    }

    public void update() {
        System.out.println("更新订单");
        int i= 1/0;
    }
}
2. 在配置文件中配置OrderServiceImpl
<bean id="orderService" class="service.impl.OrderServiceImpl"/>
3. 在测试类中添加OrderService的测试
@Autowired
private OrderService orderService;
@Test
public void testOrderService()throws Exception{
    // .... 调用对应方法
}

2.基于代码实现获取参数

在通过方法上添加JoinPoint类型的参数,通过它提供的getArgs方法进行获取

// 前置通知方法
public void beforeLog(JoinPoint joinPoint){
    // 获取切入点方法的参数们
    Object[] args = joinPoint.getArgs();
    System.out.println(Arrays.asList(args));
    // 获取目标对象的类名
    String simpleName = joinPoint.getTarget().getClass().getSimpleName();
    // 获取切入点方法名
    String methodName = joinPoint.getSignature().getName();
    System.out.println(simpleName+"的"+methodName+"方法执行前");
}

3.基于配置实现获取参数(了解)

在通知方法上添加和切入点方法相同类型相同个数的参数,通过配置让spring在调用通知方法时,将切入点方法的参数传递给通知方法。

切入点方法:

public void add(String name, int money) {
    System.out.println("添加订单..."+name+"   "+money);
}

通知方法:

public void beforeLog2(String a, int b){
    System.out.println("方法执行前,参数为:"+a+","+b);
}

AOP配置:

<aop:config>
    <aop:aspect ref="logAdvice">
        <aop:before 
           method="beforeLog2" 
           pointcut="execution(* com.itheima.service.impl.*.*(..)) &amp;&amp;args(a,b)"
           arg-names="a,b"/>
    </aop:aspect>
</aop:config>

图解:
在这里插入图片描述

4.通知中获取切入点方法的返回值

在通知方法上定义和切入点方法返回值类型一致的参数,通过配置方式让spring在调用通知方法时,将切入点方法的返回传递给通知方法。通知类型必须是后置通知<aop:after-returning/>
切入点方法:

public int findCount() {
    System.out.println("查询订单总数");
    return 10;
}

通知方法:

public void afterReturningLog2(int rtValue){
    System.out.println("方法正常执行结束,返回值为:"+rtValue);
}

AOP配置:

<aop:after-returning method="afterReturningLog2" pointcut-ref="pt1" returning="rtValue"/>

图解:
在这里插入图片描述

5.通知中获取切入点方法的异常信息

在通知方法中定义一个Exception或者Throwable类型的参数,通过配置方式让spring在调用通知方法时,将切入点方法产生异常作为参数传递给通知方法。通知类型必须是异常通知<aop:after-throwing/>,当通知方法上有两个参数时,JoinPoint必须是第一个参数。
切入点方法:

public void update() {
    System.out.println("更新订单");
    int i= 1/0;
}

通知方法:

public void afterThrowingLog(JoinPoint joinPoint,Exception e){
    // 获取目标对象的类名
    String simpleName = joinPoint.getTarget().getClass().getSimpleName();
    // 获取切入点方法名
    String methodName = joinPoint.getSignature().getName();
    System.out.println(simpleName+"的"+methodName+"方法执行产生异常后,异常原因为:"+e.getMessage());
}

AOP配置:

<aop:after-throwing method="afterThrowingLog" pointcut-ref="pt1" throwing="e"/>

图解:
在这里插入图片描述

五、基于注解的AOP

1.xml结合注解

1. 开启AOP支持
<context:component-scan base-package="com.itheima"/>
<!--开启注解AOP的支持-->
<aop:aspectj-autoproxy/>
2. 在通知类中配置切面
@Component
@Aspect
public class LogAdvice {
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    public void pt1(){
    }

    @Before("pt1()")
    public void beforeLog(JoinPoint joinPoint) {
        // 获取切入点方法的相关信息
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println(className+"类的"+methodName+"方法执行前");
    }

    @AfterReturning("pt1()")
    public void afterReturningLog(JoinPoint joinPoint) {
        // 获取切入点方法的相关信息
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println(className+"类的"+methodName+"方法正常执行后");
    }

    @AfterThrowing(value="pt1()",throwing = "t")
    public void afterThrowingLog(JoinPoint joinPoint, Throwable t) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println(className+"类的"+methodName+"方法执行出现异常,异常原因为:"+t.getMessage());
    }

    @After("pt1()")
    public void afterLog(JoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println(className+"类的"+methodName+"方法执行结束");
    }
}
3. 注解通知顺序的问题
	1.多个通知类先按照类名的字符串比较规则进行确认ascii码值小的先执行
	2.在同一个类中,多个通知方法,按照方法名的字符串比较规则进行确认ascii码值小的先执行

2. 纯注解AOP

将上方配置文件开启AOP支持替换为配置类开启AOP

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

六、CGLIB

这种代理方式不要求被增强类实现接口也可以完成

被代理类,UserServiceImpl2(没有实现接口)

public class UserServiceImpl2 {
    public void save() {
        System.out.println("水泥墙");
    }
}

提供一个方法,用于生成指定对象的代理对象

class UserServiceCglibProxy{
    public static UserServiceImpl2 createUserServiceCglibProxy(final UserServiceImpl2 userService){
        UserServiceImpl2 userServiceProxy = (UserServiceImpl2) Enhancer.create(userService.getClass(), new MethodInterceptor() {
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                if (method.getName().equals("save")){
                    // 调用原始的父类中的方法
                    Object rtValue = method.invoke(userService, args);
                    System.out.println("刮大白");
                    return rtValue;
                }
                // 其它方法,原封不动的调用
                return method.invoke(userService, args);
            }
        });
        return userServiceProxy;
    }
}

测试代码

public static void main(String[] args) {
    UserServiceImpl2 userServiceImpl = new UserServiceImpl2();
    UserServiceImpl2 userService = UserServiceCglibProxy.createUserServiceCglibProxy(userServiceImpl);
    userService.save();
}

七、综合案例

1.导入相关坐标

<dependencies>
		<!--mybatis相关包两个-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <!--mysql包一个-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--spring相关包三个-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <!--sprin整合junit-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <!--druid连接池一个-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <!--junit一个-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
        <!--aspect包aop相关一个-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>      
    </dependencies>

2.config配置类

SpringConfig 主配置类:

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy //开启aop相关注解扫描
public class SpringConfig {
}

JdbcConfig 数据库连接配置类

@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setPassword(password);
        dataSource.setUsername(username);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        return dataSource;
    }
}

MybatisConfig mybatis配置类

@Configuration
@Import(JdbcConfig.class)
public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setTypeAliasesPackage("com.itheima.domain");
        factoryBean.setDataSource(dataSource);
        return factoryBean;
    }
    @Bean
    public MapperScannerConfigurer getMapperScannerConfigurer(){
        MapperScannerConfigurer configurer = new MapperScannerConfigurer();
        configurer.setBasePackage("com.itheima.dao");
        return configurer;
    }
}

3.aop 通知类

@Component
@Aspect
public class TimeAdvice {
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    public void pt(){}
    @Around("pt()")
    public Object aroundTime(ProceedingJoinPoint point){
        try {
            long start = System.currentTimeMillis();
            Object proceed = point.proceed();
            long end = System.currentTimeMillis();
            System.out.println(end-start);
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }
}

4.service的实现类

@Service("accountService")
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void save(Account account) {
        accountDao.save(account);
    }

    public void update(Account account) {
        accountDao.update(account);
    }

    public void delete(Integer id) {
        accountDao.delete(id);
    }

    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    public List<Account> findAll() {
        return accountDao.findAll();
    }
}

5.测试类

import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class TestDemo {
    @Autowired
    AccountService accountService;
    @Test
    public void test() throws Exception {
        List<Account> all = accountService.findAll();
        System.out.println(all);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值