springAOP详解,新手必看!!

1. spring的AOP

讲springaop之前先讲一下Java的两种代理模式,静态代理与动态代理。

什么是静态代理? 代理类是自己编写的。
什么是动态代理? 代理类是jdk帮我们生成的。

1.1 静态代理

静态代理是在程序运行前手动创建代理类,代理类和目标类需要实现相同接口;

需求: 演员只负责表演,创建经纪人来代理演员,处理演员除表演之外的功能。

静态代理的步骤

	1.创建演员接口IActor和演员WangBaoQiang
	2.创建经纪人SongZhe实现IActor
	3.将WangBaoQiang作为成员变量。
	4.测试

1.创建演员接口IActor和演员WangBaoQiang类

IActor.java

package com.vv.sh;

public interface IActor {

    public void sing();

    public void dance();
}

WangBaoQiang.java

package com.vv.sh.dao;

import com.vv.sh.pojo.Account;package com.vv.sh;

public class WangBaoQiang implements IActor {
    @Override
    public void sing() {
        System.out.println("王宝强唱歌");
    }
    @Override
    public void dance() {
        System.out.println("王宝强跳舞");
    }
}

2.创建经纪人SongZhe实现IActor,将WangBaoQiang作为成员变量。

SongZhe.java

package com.vv.sh;

public class SongZhe implements IActor {


    private WangBaoQiang wangBaoQiang;

    public SongZhe(WangBaoQiang wangBaoQiang) {
        this.wangBaoQiang = wangBaoQiang;
    }

    @Override
    public void sing() {
        //增强的功能
        System.out.println("唱歌的经费的洽谈");
        //调用原有功能
        wangBaoQiang.sing();
    }

    @Override
    public void dance() {
        //增强的功能
        System.out.println("跳舞的经费的洽谈");
        //调用原有功能
        wangBaoQiang.dance();
    }
}

3.测试

public class Test {

    public static void main(String[] args) {
        WangBaoQiang wangBaoQiang = new WangBaoQiang();
        IActor actor = new SongZhe(wangBaoQiang);

        //执行代理对象的方法
        actor.dance();
        actor.sing();
    }
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HIYPGsRX-1603535102760)(C:\Users\Ali\AppData\Roaming\Typora\typora-user-images\1603523571524.png)]

小结:静态代理的问题

如果目标类中有多个方法都需要增强,我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改

1.2 动态代理

代理类在程序运行时创建的方式被称为动态代理。
优势:相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

jdk的动态代理是基于接口的实现类,所以必须要有接口
cglib代理是基于子类继承父类的,不需要接口。

1.2.1 jdk的动态代理

目标:使用jdk的动态代理针对WangBaoQiang进行代理

1、JDK动态代理:基于接口的;
2、JDK动态代理实现要点:
		Proxy类
		newProxyInstance静态方法
		InvocationHandler增强方法
		
package com.vv.sh;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//jdk的动态代理
public class Test {

    public static void main(String[] args) {
        WangBaoQiang wangBaoQiang = new WangBaoQiang();
		ClassLoader classLoader = wangBaoQiang.getClass().getClassLoader();
        Class<?>[] interfaces = wangBaoQiang.getClass().getInterfaces();
        /*
          参数1:类加载器
          参数2:接口
          参数3: 处理器对象
         */
        IActor songZhe = (IActor) Proxy.newProxyInstance(classLoader, interfaces,
                new InvocationHandler() {
            /**
             *
             * @param proxy  代理对象本身,songzhe
             * @param method  被代理对象执行的方法
             * @param args   方法传递的参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                //判断方法名
                if("sing".equals(methodName)){
                    System.out.println("洽谈唱歌的费用");
                }else if("dance".equals(methodName)){
                    System.out.println("洽谈跳舞的费用");
                }
                //执行原有功能
                method.invoke(wangBaoQiang,args);
                return null;
            }
        });
        songZhe.sing();
        songZhe.dance();
    }
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gzwAbrFy-1603535102764)(C:\Users\Ali\AppData\Roaming\Typora\typora-user-images\1603527827841.png)]

jdk生成的代理类如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d9i58pBN-1603535102766)(C:\Users\Ali\AppData\Roaming\Typora\typora-user-images\1603528135009.png)]

1.2.2 cglib动态代理

如果目标类没有实现接口呢?
	那么就无法使用JDK的动态代理,因此这种方式有其局限性,必须实现一个接口。
可以使用的方案:
	使用cglib动态代理:基于子类继承父类的,不需要接口。

实现步骤:

	1.引入cglib的依赖
	2.编写wangbaoqiang,不需要使用IActor接口
	3.编写cglib的api创建代理对象

​ 1.引入cglib的依赖

		<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.1</version>
        </dependency>

​ 2.编写wangbaoqiang类,不需要使用IActor接口

package com.vv.sh;

public class WangBaoQiang  {

    public void sing() {
        System.out.println("王宝强唱歌");
    }


    public void dance() {
        System.out.println("王宝强跳舞");
    }
}

​ 3.编写cglib的api创建代理对象

package com.vv.sh;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class Test {

    public static void main(String[] args) {
        WangBaoQiang wangBaoQiang = new WangBaoQiang();

        //cglib的api
        /**
         *      参数1:  代理对象需要继承的类,
         *      参数2: 处理器,作用类似jdk动态代理中的InvocationHanlder
         */
        WangBaoQiang songzhe = (WangBaoQiang) Enhancer.create(WangBaoQiang.class, new MethodInterceptor() {
            /**
             *
             * @param o     代理对象本身 
             * @param method  被代理的方法
             * @param objects 方法的参数
             * @param methodProxy  代理方法本身,没有暖用
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //增强的功能
                String methodName = method.getName();
                if("sing".equals(methodName)){
                    System.out.println("洽谈唱歌的费用");
                }else if("dance".equals(methodName)){
                    System.out.println("洽谈跳舞的费用");
                }

                //原有功能
                method.invoke(wangBaoQiang,objects);

                return null;
            }
        });
        songzhe.dance();
        songzhe.sing();
    }
}

1.3 springaop的XML方式

1.3.1 aop介绍

aop:意思是面向切面的编程,就是采用动态代理技术,在不修改源码的情况下,对功能进行增强。

上面已经讲解了动态代理,下面看下spring的aop是如何实现的

1.3.2 相关专业术语

aop中有几个比较重要的相关专业术语

切面:用来配置增强与切入点的关系
切入点:需要被增强的方法
增强:也叫通知,增强的功能所在的方法
需求:
	操作数据库Account表,查询所有账户信息
	使用spring的aop,完成日志的输出

1.3.3 步骤:

环境准备

​ 1.引入依赖,spring-context,junit,spring-test,mysql驱动,spring-jdbc

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.38</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.9</version>
    </dependency>

2.编写实体类,service、dao层的接口和实现类

实体类

package com.vv.pojo;

public class Account {
    private int id;
    private String accountName;
    private double money;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", accountName='" + accountName + '\'' +
                ", money=" + money +
                '}';
    }
}

service接口和实现类

package com.itheima.service;
import com.itheima.pojo.Account;
import java.util.List;

public interface AccountService {
    public List<Account> findAllAccounts();
}
package com.vv.service.impl;
import com.vv.dao.AccountDao;
import com.vv.pojo.Account;
import com.vv.service.AccountService;
import java.util.List;

public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Override
    public List<Account> findAllAccounts() {

        return accountDao.findAllAccounts();
    }
}

dao接口和实现类

package com.vv.dao;
import com.vv.pojo.Account;
import java.util.List;

public interface AccountDao {
    List<Account> findAllAccounts();
}
package com.vv.dao.impl;

import com.vv.dao.AccountDao;
import com.vv.pojo.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;
public class AccountDaoImpl implements AccountDao {
    private JdbcTemplate jdbcTemplate;
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    @Override
    public List<Account> findAllAccounts() {
        String sql = "select * from account";
        List<Account> accountList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Account.class));
        return accountList;
    }
}

​ 3.编写spring的配置

applicationContetx.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">

    <!-- 引入外部配置-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!-- bean definitions here -->
    <bean id="accountService" class="com.vv.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <bean id="accountDao" class="com.vv.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
    </bean>


    <!--
        aop的配置:
            在 1切面中  配置  2切入点  和  3增强 的 4关系
    -->
    <!-- 创建增强所在的对象-->
    <bean id="logService" class="com.vv.service.impl.LogServiceImpl"></bean>
    <!--声明开始配置aop-->
    <!-- 注意名称空间是aop的名称空间-->
    <aop:config>

        <!--声明切面
            ref: 增强所在的对象
        -->
        <aop:aspect ref="logService">
            <!--
                配置增强和切入点的关系
                before:前置通知,增强在核心功能之前执行。
                method: 增强
                pointcut: 切入点
            -->

            <!--
                切入点表达式:
                    语法:
                        修饰符  返回值   包名.类名.方法名(参数)

                    1.修饰符可以省略不写
                    2.*可以代表任意返回值
                           * com.vv.service.impl.AccountServiceImpl.findAllAccounts(..)
                    3.*可以代表任意包名,但是一级包,需要一个*
                            * *.*.*.*.AccountServiceImpl.*(..)
                    4. *..代表多级包
                            * *..AccountServiceImpl.*(..)
                    5. *可以代表任意类名
                              * *..*.*(..)
                    6. *可以代表任意方法名
                            * com.vv.service.impl.AccountServiceImpl.*(..)
                    7. *可以代表任意参数,但是一个*标识一个参数
                            * com.vv.service.impl.AccountServiceImpl.*(*)
                    8. ..代表任意多个参数
                        * com.vv.service.impl.AccountServiceImpl.*(..)

                     在实际企业开发,常用的切入点表达式
                        应该在确保精准的情况下,保证其简洁。

                        * com.vv.service.impl.*.*(..)

            -->

            <!--
                aop:before: 前置通知,增强在切入点之前执行
                aop:after:最终通知,无论如何都会执行
                aop:after-returning :后置通知,没有错误的情况下才会执行
                aop:after-throwing: 异常通知,出现异常会执行
                aop:around: 环绕通知,自定义通知的位置
            -->

<!--
            <aop:before method="printLog" pointcut="execution(* com.vv.service.impl.AccountServiceImpl.*(..))"></aop:before>
-->
                <!--抽取切入点表达式-->
            <aop:pointcut id="pt1" expression="execution(* com.vv.service.impl.AccountServiceImpl.*(..))"></aop:pointcut>
<!--
            <aop:after-returning method="printLog" pointcut-ref="pt1"></aop:after-returning>
-->
<!--
            <aop:after-throwing method="printLog" pointcut-ref="pt1"></aop:after-throwing>
-->
<!--
            <aop:after method="printLog" pointcut-ref="pt1"></aop:after>
-->
            <aop:around method="printLog2" pointcut-ref="pt1"></aop:around>

        </aop:aspect>
    </aop:config>
</beans>

jdbc.properties

4.编写测试类进行测试

package com.vv.service;

import com.vv.pojo.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;
    @Test
    public void findAllAccounts() {
        List<Account> allAccounts = accountService.findAllAccounts();
        for(Account a:allAccounts){
            System.out.println(a);
        }
    }
}
XML方式
步骤:
	1.引入spring-aspect依赖
	2.编写增强类
	3.配置aop
		a.<aop:config> 声明开始配置aop
		b.<aop:aspect> 声明切面
		c.<aop:before point-cut="切入点" method='增强'>
			before:前置通知(就是关系)

1.引入spring-aspect依赖

	<!--spring的aop相关-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>

2.编写增强类

package com.vv.service;

public interface LogService {
    public void printLog();
}
package com.vv.service.impl;

import com.vv.service.LogService;

public class LogServiceImpl implements LogService {
    @Override
   public void printLog() {
        System.out.println("调用方法前,输入日志信息,******");
    }
}

3.配置aop

<!-- 创建增强所在的对象-->
    <bean id="logService" class="com.vv.service.impl.LogServiceImpl"></bean>
    <!--声明开始配置aop-->
    <!-- 注意名称空间是aop的名称空间-->
    <aop:config>
        <!--声明切面
            ref: 增强所在的对象
        -->
        <aop:aspect ref="logService">
            <!--
                配置增强和切入点的关系
                before:前置通知,增强在核心功能之前执行。
                method: 增强
                pointcut: 切入点
            -->
            <!--
                切入点表达式:
                    语法:
                        修饰符  返回值   包名.类名.方法名(参数)
                    1.修饰符可以省略不写
                    2.*可以代表任意返回值           
  * com.vv.service.impl.AccountServiceImpl.findAllAccounts(..) 
                    3.*可以代表任意包名,但是一级包,需要一个*
                            * *.*.*.*.AccountServiceImpl.*(..)
                    4. *..代表多级包
                            * *..AccountServiceImpl.*(..)
                    5. *可以代表任意类名
                              * *..*.*(..)
                    6. *可以代表任意方法名
                            * com.vv.service.impl.AccountServiceImpl.*(..)
                    7. *可以代表任意参数,但是一个*标识一个参数
                            * com.vv.service.impl.AccountServiceImpl.*(*)
                    8. ..代表任意多个参数
                        * com.vv.service.impl.AccountServiceImpl.*(..)
                     在实际企业开发,常用的切入点表达式
                        * com.vv.service.impl.*.*(..)
            -->
            <!--
                aop:before: 前置通知,增强在切入点之前执行
                aop:after:最终通知,无论如何都会执行
                aop:after-returning :后置通知,没有错误的情况下才会执行
                aop:after-throwing: 异常通知,出现异常会执行
                aop:around: 环绕通知,自定义通知的位置
            -->
            <aop:before method="printLog" pointcut="execution(* com.vv.service.impl.AccountServiceImpl.*(..))">
            </aop:before>
         </aop:aspect>
    </aop:config>

1.4 springAOP的半XML半注解方式

实现步骤:

1.复制上述工程
	2.ioc注解(详细步骤省略)
	3.使用注解aop来替代xml的aop
		a. <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 开启注解aop
		b. @Aspect:声明切面类
		c. @Before添加到通知上:声明关系,value属性就是切入点表达式
		d: @PointCut抽取公共的切入点表达式
@Override
    @Before("execution(* com.vv.service.impl.AccountServiceImpl.findAllAccounts(..))")
    public void printLog(JoinPoint joinPoint) {
        //获取切入点的签名信息,签名信息包含了切入点的名字,方法,类等等。。。
        Signature signature = joinPoint.getSignature();
        //获取切入点的方法名字
        String name = signature.getName();

        System.out.println("开始执行方法----"+name);
    }

1.5 springAOP的纯注解方式

纯注解就是用配置类代替配置文件所有配置

关于开启aop注解的注解@EnableaApectjAutoproxy	

步骤:
	1.复制上述工程:
	2.创建一个类,使用@Configuration声明配置类
	3.@PropertySource引入外部配置
	4.@ComponementScan开启注解扫描
	5.@Bean创建对象
	6.@Import导入其他配置类
	7.@EnableAspectjAutoproxy

SpringConfig.java

package com.vv.config;

import org.springframework.context.annotation.*;

@Configuration//声明当前是一个配置类
@PropertySource("classpath:jdbc.properties")// 引入外部配置
@ComponentScan("com.vv") //开启注解扫描
@EnableAspectJAutoProxy //开启注解aop
@Import(JdbcConfig.class)
public class SpringConfig {
}

JdbcConfig.java

package com.vv.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;


public class JdbcConfig {

    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Value("${jdbc.driverClass}")
    private String driverClass;

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

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        return jdbcTemplate;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值