第三阶段(day05)spring-aop

1.复习

新建项目

①.引入jar包

javax.annotation-api这个jar包是各种注解需要依赖的包,jdk8自身包含,不用加。jdk11移除了该包,需要加入。

日志包是4个spring核心包需要依赖的包。

再引入druid和mysql-connection-java这两个包。

②.建xml文件

建spring.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/context 
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--1.开包扫描
     对带有@Controller @Service @Repository @Component 进行IOC注册,注册到容器中
     对注册到容器中的bean对象,带有@Autowired @Resource 的di注解生效,用来维护对象间的关系
     -->
    <context:component-scan base-package="com.javasm"></context:component-scan>

</beans>

建dao.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="initialSize" value="${jdbc.initSize}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
    </bean>

</beans>

建jdbc.properties

jdbc.url=jdbc:mysql://127.0.0.1:3306/704A?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=root
jdbc.initSize=4
jdbc.maxActive=10

让spring.xml文件作为主配置文件,容器中只加载这一个文件即可:

<context:component-scan base-package="com.javasm"></context:component-scan>

<!--导入其他配置文件-->
<import resource="dao.xml"></import>

③.写接口及实现类

com.javasm.sys.service.IUserService

public interface IUserService {
    String getUnameById(Integer id);

    String getUphoneByUname(String uname);
}

(写接口习惯I开头)

com.javasm.sys.service.impl.UserServiceImpl

@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private DataSource ds;

    @Override
    public String getUnameById(Integer id) {
        Connection connection = null;
        try {
            connection = ds.getConnection();
            String sql = "select * from sys_user where id=?";
            PreparedStatement pst = connection.prepareStatement(sql);
            pst.setInt(1,1);
            ResultSet resultSet = pst.executeQuery();
            resultSet.next();
            String uname = resultSet.getString("uname");
            return uname;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return null;
    }

    @Override
    public String getUphoneByUname(String uname) {
        return null;
    }


}

测试:

com.javasm.TestApplicationContext:

public class TestApplicationContext {
    @Autowired
    private IUserService us;
    
    @Test
    public void test1_loadXML(){
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        IUserService us = ac.getBean(IUserService.class);
        String unameById = us.getUnameById(1);
        System.out.println(unameById);
        ac.close();
    }
}

2.junit与spring整合测试

目的:把测试类也注册进入容器,然后需要测试哪个对象,就注入哪个对象即可.

添加spring-test.jar

在测试类添加@Runwith(SpringJunit4ClassRunner.class)

在测试类添加@ContextConfiguration("classpath:spring.xml")

爆红是因为它默认找测试资源下的路径,不是Resources资源下的文件,故需要写绝对路径。

解析到classpath:开头的字符串后,会从类路径下找配置文件。(resource,src都是类路径)

//SpringJUnit4ClassRunner类用来加载指定的配置文件,初始化容器,并把指定的对象TestApplicationContext也注册到容器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TestApplicationContext {
    @Autowired
    private IUserService us;

    @Test
    public void test1_loadXML(){
        String unameById = us.getUnameById(1);
        System.out.println(unameById);
    }
}

后续用springboot做项目用不着这些,一句话搞定。

3.什么是aop(***重要)

aop:面向切面对象编程,是对oop面向对象编程的一种补充与完善.(它本身也是一种oop编程)

我们常规的对象是领域模型对象(某个业务领域需要用到的对象,对应数据库就是业务表),即实体类,之前说的面向对象编程都是面向领域模型对象。给我们一个业务,会先分析该业务有哪些领域模型对象,比如贷款业务(贷款人,贷款机构,征信记录,贷款人的担保信息,放款业务员)。

当接触到一个新的业务,以oop的思想来提取出该业务相关的领域模型对象,提取出domain对象中的属性与方法,使用rose(一个工具)进行uml模型设计与分析,创建好数据库表.进行核心业务(申请,审核,放款)的编码实现.

此时为了数据安全,需要在进行参数的校验,需要在每一步业务执行的时候记录执行日志.类似日志,参数校验的业务称为辅助业务(非核心的业务),这些业务也就是切面对象.

aop:面向公共的非核心业务的辅助对象进行代码开发,常见:日志,数据校验,数据加密解密,事务控制.以代理模式把切面对象中的方法 插入 到核心对象方法的前后异常最终处.

补充:
POJO等价javaBean等价java对象

domain等价entity等价实体类等价领域模型对象

4.aop中的几个概念

1.目标对象(target):原生对象,此对象中的方法需要被扩展.需要被创建代理的对象(AlipayImpl.WechatPayImpl)

2.连接点方法(joinpoint):目标对象中的需要被扩展的方法,就是连接点方法

3.织入(weave): 在程序运行期间,以动态代理模式在工厂中在目标对象的连接点方法 的不同位置 插入 切面对象中的通知方法.

4.切面对象(aspect):辅助业务对象

5.通知方法(advisor):切面对象中的方法,分为前置通知,返回通知,异常通知,最终通知,环绕通知

6.切入点(pointcut):通过描述方法结构或者通过指定注解来描述一些连接点方法的集合.

5.spring中的aop注解使用(重要)

spring对aop进行了封装实现.spring本身就是一个工厂,内部注册了bean对象,spring内可以对这些bean对象进行再加工.

①.添加相关jar包

添加spring-aop;spring-aspect两个依赖包

aspectj第三方aop组件包.

添加cglib动态代理包,类似Proxy工具类的作用,cglib支持无接口对象创建代理.(使用Proxy工具类要求被代理对象必须有接口,实际上在spring里我们会确保service都会有接口)

②.在spring.xml文件中开启aop注解识别.

spring.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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.javasm"></context:component-scan>

    <import resource="dao.xml"></import>

    <!--开启aop注解识别,识别sapectj组件的安排注解:@Aspect,@Pointcut,@Before,@AfterReturning,@Afterthrowing,@After,@Around
     这些注解生效的前提:对象注册进入容器
     proxy-target-class默认false,如果目标对象有接口,则使用Proxy创建代理类,没有接口则使用cglib组件创建代理类。若属性值
     为true,则为目标类创建代理全部用cglib组件。故可完全不加它
    -->
    <aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>

</beans>

③.定义切面bean对象

④.定义切入点表达式

⑤.定义通知方法

拿复习中的例子:

//目标对象
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private DataSource ds;

    //连接点方法
    @Override
    public String getUnameById(Integer id) {
        Connection connection = null;
        try {
            connection = ds.getConnection();
            String sql = "select * from sys_user where id=?";
            PreparedStatement pst = connection.prepareStatement(sql);
            pst.setInt(1,1);
            ResultSet resultSet = pst.executeQuery();
            resultSet.next();
            String uname = resultSet.getString("uname");
            return uname;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return null;
    }

    @Override
    public String getUphoneByUname(String uname) {
        return null;
    }


}

新建src.com.javasm.sys.aspect.LogAspect

//切面对象
@Component   //bean对象注册到容器
@Aspect      //表示是一个切面bean对象
public class LogAspect {

    //通过切入点表达式定义目标对象与连接点方法(字符串中指明类型以及对应的类中的方法,包括方法形参)
    @Pointcut("execution(java.lang.String com.javasm.sys.service.impl.UserServiceImpl.getUnameById(java.lang.Integer))")
    public void servicepc(){
    }

    //该注解表示:beforeAdvise方法作为前置通知织入到servicepc方法上的切入点表达式
    @Before("servicepc()")
    public void beforeAdvise(){
        System.out.println("前置通知");
    }

    @AfterReturning("servicepc()")
    public void returningAdvise(){
        System.out.println("返回通知");
    }

    @AfterThrowing("servicepc()")
    public void throwingAdvise(){
        System.out.println("异常通知");
    }

    @After("servicepc()")
    public void afterAdvise(){
        System.out.println("最终通知");
    }
}

测试:

先走前置通知,再getUnameById,再返回通知,没有异常不走异常通知(若有异常,必须抛出去才行),最后走最终通知。

看下在通知方法中能够获取的数据:

前置通知能获得目标对象类名,目标对象方法的参数

返回通知能获得核心业务方法的返回值(前置通知能得到的,返回通知都能得到)

异常通知能获得核心业务方法产生的异常信息 (肯定得不到返回值,已经异常了。也可得到前置通知得到的)

最终通知可获得前置通知获得的,得不到返回值和异常信息。

@Component
@Aspect
public class LogAspect {

    @Pointcut("execution(java.lang.String com.javasm.sys.service.impl.UserServiceImpl.getUnameById(java.lang.Integer))")
    public void servicepc(){
    }

    //获取哪个类里的哪个方法以及哪些实参,只需在该方法加JoinPoint连接点对象。
    //JoinPoint对象包含目标对象,连接点方法信息,传入的实参信息
    @Before("servicepc()")
    public void beforeAdvise(JoinPoint jp){
        Object target = jp.getTarget();  //目标对象
        MethodSignature signature = (MethodSignature)jp.getSignature(); //连接点方法对象
        Method method = signature.getMethod();  //目标方法
        Object[] args = jp.getArgs();   //实参数组
        Object aThis = jp.getThis();     //底层的代理对象
        System.out.println("前置通知");
    }

    //获取目标方法返回值,返回值类型用Object,是因为后续可能织入到其他方法中
    @AfterReturning(value="servicepc()",returning = "o")
    public void returningAdvise(JoinPoint jp,Object o){
        String name = jp.getTarget().getClass().getName();
        System.out.println("目标类名:"+name+",返回值:"+o);
        System.out.println("返回通知");
    }

    //也可得到前置通知的信息,参数传入JoinPoint jp即可
    @AfterThrowing(value = "servicepc()",throwing = "e")
    public void throwingAdvise(Exception e){
        System.out.println("异常通知"+e);
    }

    @After("servicepc()")
    public void afterAdvise(){
        System.out.println("最终通知");
    }
}

以上是切入点表达式的完整的写法,只对一个方法做织入。

实际开发中不可能出现只对某一个方法做织入的,因为这样的话没必要做面向切面,直接在方法中写入即可。

以下用通配的方法:

//不限制方法的返回值类型,不限功能模块,不限类名和方法名,不限参数类型和长度
@Pointcut("execution(* com.javasm.*.service.impl.*.*(..))")
public void servicepc(){
}
//也可指定方法名的前缀
@Pointcut("execution(* com.javasm.*.service.impl.*.get*(..))")
public void servicepc(){
}

切入点表达式的第二种写法:

通过自定义注解定义目标对象与连接点方法

新建com.javasm.sys.aspect.Mylog

//注解的定义需要元注解的声明
//@Target表示该注解注解到哪个位置,ElementType.TYPE注解到类上,ElementType.METHOD注解到方法上
@Target({ElementType.TYPE,ElementType.METHOD})
//@Retention生命周期,RetentionPolicy.RUNTIME运行时注解,会保留在字节码文件,代码环境下可得到该注解信息。
//@Override是编译时注解或者说源码注解,不能保留在字节码文件
@Retention(RetentionPolicy.RUNTIME)
public @interface Mylog {
    String sname() default "";
    String value() default "";
}

//annotation里写注解
@Pointcut("@annotation(com.javasm.sys.aspect.Mylog)")
public void servicepc(){
}

//beforeAdvise方法作为前置通知织入到带有Mylog注解的方法上
@Before("servicepc()")
public void beforeAdvise(){
    System.out.println("前置通知");
}

如下,有注解的可以生效:

也可在注解上加信息

此时连接点对象里还有注解的信息。

@Before("servicepc()")
public void beforeAdvise(JoinPoint jp){
    Object target = jp.getTarget();
    MethodSignature signature = (MethodSignature)jp.getSignature();
    Method method = signature.getMethod();
    Object[] args = jp.getArgs();
    Object aThis = jp.getThis();

    //获取连接点方法的注解信息
    Class<?> aClass = target.getClass();
    try {
        Method method1 = aClass.getMethod(method.getName(), method.getParameterTypes());
        Mylog annotation = method1.getAnnotation(Mylog.class);
        String sname = annotation.sname();
        String value = annotation.value();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }
    System.out.println("前置通知");
}

⑥.使用环绕通知取到前四类通知

前面的四个通知可忘记

建com.javasm.sys.aspect.tx

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface tx {
    //单纯作为一个注解,不承载数据
}

建com.javasm.sys.aspect.TxAspect

@Component
@Aspect
public class TxAspect {

    @Pointcut("@annotation(tx)")
    public void pc(){
    }

    //环绕通知
    //ProceedingJoinPoint是JoinPoint的子接口,有了新的方法。是Target,Method,实参,代理对象的封装
    public Object doTx(ProceedingJoinPoint jp){
        Object result = null;

        Object target = jp.getTarget();
        Object[] args = jp.getArgs();
        MethodSignature signature = (MethodSignature)jp.getSignature();
        Method method = signature.getMethod();

        try {
            System.out.println("前置通知");
            result = jp.proceed();  //执行核心业务方法
            System.out.println("返回通知,返回值为:"+result);
        } catch (Throwable throwable) {
            System.out.println("异常通知,异常对象为:"+throwable);
        }finally {
            System.out.println("最终通知");
        }

        return result;  //返回核心业务方法的返回值
    }

}

事务管理切面:

前置通知里从连接池打开连接

返回通知里提交连接

异常通知里回滚

最终通知关闭连接

这样,service层,dao层不用管这些操作。

6.spring中的aop的xml配置使用

后续不再用xml文件。

以xml配置的方式来配置切面,切入点,通知方法.

新建com.javasm.sys.aspect.LogAspect2

public class LogAspect2 {

    public void beforeAdvise(JoinPoint jp){
        System.out.println("前置通知");
    }

    public void returningAdvise(JoinPoint jp,Object O){
        System.out.println("返回通知");
    }

    public void throwingAdvise(Exception e){
        System.out.println("异常通知");

    }

    public void afterAdvise(){
        System.out.println("最终通知");
    }
}

在spring配置文件里:

<aop:config>
    <!--定义切入点表达式
    expression还是两种写法
    -->
    <aop:pointcut id="pc2" expression="@annotation(com.javasm.sys.aspect.tx)"/>
    <!--定义切面bean-->
    <aop:aspect ref="logAspect2">
         <aop:before method="beforeAdvise" pointcut-ref="pc2"></aop:before>
         <aop:after-returning method="returningAdvice" returning="o" pointcut-ref="pc2"></aop:after-returning>
         <aop:after-throwing method="throwingAdvice" throwing="e" pointcut-ref="pc2"></aop:after-throwing>
         <aop:after method="afterAdvice" pointcut-ref="pc2"></aop:after>
    </aop:aspect>
</aop:config>

若为环绕通知:

public class LogAspect2 {

    public Object adAround(ProceedingJoinPoint jp){
        Object proceed = null;
        try {
            System.out.println("前置通知");
            proceed = jp.proceed();
            System.out.println("返回通知");
        } catch (Throwable throwable) {
            System.out.println("异常通知"+throwable);
        }finally {
            System.out.println("最终通知");
        }
        return proceed;
    }
}

<aop:config>
    <aop:pointcut id="pc2" expression="@annotation(com.javasm.sys.aspect.tx)"/>
    <aop:aspect ref="logAspect2">
        <aop:around method="doAround" pointcut-ref="pc2"></aop:around>
    </aop:aspect>
</aop:config>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值