SSM

SSM框架系列学习总结2之Spring AOP

2018-01-20  本文已影响5人  梦蓝樱飞2020

先整理AOP之前, 我先把之前DI的内容整理完!

DI

Spring的依赖注入第二种方式:

通过构造方法注入属性
首先提供一个实体类, 不过这个类没有属性的get和set方法, 只有一个有参的构造方法和toString方法.

public class Boy {
    private String id;
    private String name;
    private Integer age;
    private Double salary;

    public Boy(String id, String name, Integer age, Double salary) {
        System.out.println("IOC容器创建对象");
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Boy{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

Spring配置

    <!--
        通过constructor实现依赖注入
        constructor-arg: 通过构造方法传过去的值
        type: 用来限制传递的参数的类型
    -->
    <bean id="boyId" class="com.wtu.spring.di.constructor.Boy">
        <!--
        <constructor-arg type="java.lang.String">
            <value>001</value>
        </constructor-arg>
        <constructor-arg type="java.lang.String">
            <value>小阳</value>
        </constructor-arg>
        <constructor-arg type="java.lang.Integer">
            <value>16</value>
        </constructor-arg>
        <constructor-arg type="java.lang.Double">
            <value>1000</value>
        </constructor-arg>
        -->

        <!-- index: 参数在构造方法中的位置, 第一个参数从0开始 -->
        <constructor-arg index="0">
            <value>001</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>小阳</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>16</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>1000</value>
        </constructor-arg>
    </bean>

测试类:

        // 启动IOC容器
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                new String[]{"com/wtu/spring/di/constructor/spring3.0.xml"}
        );

        Boy boy = (Boy) ac.getBean("boyId");
        System.out.println(boy);
        //Boy{id='001', name='小阳', age=16, salary=1000.0}

注入Date类型:
在依赖注入中 如何将一个字符串转换成Date对象, 注入到目标对象

public class Boy {
    private String id;
    private String name;
    private Integer age;
    private Date birthday;
    private Double salary;

    public Boy() {
        System.out.println("Ioc容器创建对象");
    }

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Boy{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", birthday=" + birthday +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

Spring配置

    <!-- 注册时间格式转换器
        -->
    <bean id="format" class="java.text.SimpleDateFormat">
        <constructor-arg>
            <!-- 时间的格式样式 -->
            <value>yyyy-MM-dd</value>
        </constructor-arg>
    </bean>

    <!-- 注册bean对象
        new SimpleDateFormat("yyyy-MM-dd").parse("2017-12-12"); 
    -->
    <bean id="boy" class="com.wtu.spring.di.set.Boy">
        <property name="birthday">
            <bean factory-bean="format" factory-method="parse">
                <constructor-arg>
                    <value>2017-12-12</value>
                </constructor-arg>
            </bean>
        </property>
    </bean>

测试类:

        // 启动IOC容器
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                new String[]{"com/wtu/spring/di/set/spring3.0.xml"}
        );

        Boy boy = (Boy) ac.getBean("boy");
        System.out.println(new SimpleDateFormat("yyyy-MM-dd").parse("2017-12-12"));
        //Tue Dec 12 00:00:00 CST 2017
        System.out.println(boy);
        //Boy{id='null', name='null', birthday=Tue Dec 12 00:00:00 CST 2017, age=null, salary=null}
DI注解形式
结构.png

UserDaoImpl.java:

public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("IOC容器创建对象");
    }

    @Override
    public void addUser() {
        System.out.println("添加用户");
    }
}

UserServiceImpl.java:

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public UserServiceImpl() {
        System.out.println("IOC容器创建UserServiceImpl对象");
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void addUser() {
        System.out.println("service中addUser方法被调用了");
    }
}

Spring配置:

    <!--注册UserDao对象 -->
    <bean id="userDao" class="com.wtu.spring.di.annotation.UserDaoImpl"/>

    <!--注册UserService对象 -->
    <bean id="userService" class="com.wtu.spring.di.annotation.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>

测试类:

        // 启动IOC容器
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                new String[]{"com/wtu/spring/di/annotation/spring3.0.xml"}
        );

        UserService us = (UserService) ac.getBean("userService");

运行结果:


结果.png

注解改写:
后来的Spring配置文件:

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

    <!-- 开启Spring注解的功能 -->
    <context:annotation-config/>
    <!-- 让springIOC容器自动去扫描某一个包, 创建该包下的bean对象 -->
    <context:component-scan base-package="com.wtu.spring.di.annotation2"/>

</beans>

UserDaoImpl.java:

/**
 * @Component(value = "userDao")
 *  告诉SpringIOC容器创建一个id为userDao的bean对象
 *  value可以省略, 但是此时生成的id为类名, 并且首字母小写
 */
//@Component(value = "userDao") // userDaoImpl
@Repository(value = "userDao")
public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("IOC容器创建对象");
    }

    @Override
    public void addUser() {
        System.out.println("添加用户");
    }
}

UserServiceImpl.java:

@Service(value = "userServiceImpl")
public class UserServiceImpl implements UserService {
    @Resource(name = "userDao")
//    @Autowired
    private UserDao userDao;

    public UserServiceImpl() {
        System.out.println("IOC容器创建UserServiceImpl对象");
    }

    /*
        spring会自动查找id为userDao的bean对象, 如果找到就通过下面的set方法注入进来
     */
//    @Resource(name = "userDao")
//    public void setUserDao(UserDao userDao) {
//        this.userDao = userDao;
//    }

    @Override
    public void addUser() {
        System.out.println("service中addUser方法被调用了");
    }
}

测试类:

        // 启动IOC容器
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                new String[]{"com/wtu/spring/di/annotation2/spring3.0.xml"}
        );

        UserService us = (UserService) ac.getBean("userServiceImpl");
        us.addUser();

运行结果:

结果.png
注意: @Component现在不提倡使用
对于注册bean对象的注解现在提倡使用如下三个:
@Controller 一般作用在springMVC中
@Service 作用在业务层
@Repository 作用在持久层
但是不是绝对的, 任何一个在任何一层都可以用

AOP

AOP.jpeg AOP.jpeg AOP.jpeg

如图上述的解决方案不好:
在javaweb三层框架中,dao层是数据访问层,它的核心业务就是操作数据库,然而像输出时间,开启事务, 日志输出等等。这些事情都不是核心业务,我们可以称之为服务代码,我们不能将执行服务代码这样的事情交给dao层来做。但是我们又需要这样的服务代码。所以我们可以找该类的一个代理对象来实现。

动态代理图解:


动态代理图解.png
动态代理核心代码模拟实现

日志类, 切面:

public class MyLog {
    @SuppressWarnings("deprecation")
    public void writeConsole() {
        System.out.println(new Date().toLocaleString());
    }
}

目标对象所在类, 执行核心代码:

public class UserDaoImpl implements UserDao {

    @Override
    public void addUser() {
        System.out.println("添加用户...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}

模拟JDK动态代理的Proxy类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 中间类, 相当于JDK动态代理中的Proxy
 * @Author menglanyingfei
 * @Created on 2018.01.17 16:51
 */
public class Middle {
    private UserDao userDao = new UserDaoImpl();
    // 定义切面
    private MyLog myLog = new MyLog();

    public Object getObject() {
        /*
        第一个参数: 当前类的类加载器
        第二个参数: 代理对象实现的接口类型
        第三个参数: 是个接口, 在这个接口中具体去处理如何调用切面以及目标对象
            匿名内部类
         */
        return Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                userDao.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     *
                     * @param proxy 目标对象
                     * @param method 需要执行目标对象的方法对应的Method对象
                     * @param args 执行目标对象业务方法时需要的参数
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 定义返回值, 目标对象方法执行以后的返回值
                        Object result = "";
                        // 获取目标对象的方法名
                        String methodName = method.getName();
                        if ("addUser".equals(methodName)) {
                            myLog.writeConsole();
                            result = method.invoke(userDao, args);
                            myLog.writeConsole();
                        } else {
                            result = method.invoke(userDao, args);
                        }
                        return result;
                    }
                }
        );
    }

}

测试类:

        // 通过一个中间类来获取代理对象
        Middle middle = new Middle();
        UserDao userDao = (UserDao) middle.getObject();
        userDao.addUser();


        System.out.println(userDao);
        //com.wtu.spring.aop.base.UserDaoImpl@5305068a
        // 红色标识显示
        System.err.println(userDao.getClass());
        // 使用JDK动态代理产生的代理对象
        //class com.sun.proxy.$Proxy0

运行结果:


运行结果.png

导入aop的所需jar包:


jar.png
SpringAOP的五种通知方式

通知就是告诉代理在什么时候去执行服务代码。
1.前置通知: 执行目标对象业务方法之前执行服务代码(切面)
2.后置通知: 执行目标对象业务方法之后执行服务代码
3.方法正常返回通知:
4.方法抛出异常通知:
5.环绕通知:

由于晚上太想睡觉了, 所以只先总结到前置通知, 把剩下的内容留到下次总结吧!
先弄一下前置通知吧!
先定义一个切面: 输出日志时间类

package com.wtu.spring.aop.before;

import java.util.Date;

/**
 * 切面
 * @Author menglanyingfei
 * @Created on 2018.01.17 17:06
 */
public class MyLog {
    @SuppressWarnings("deprecation")
    public void writeConsole() {
        System.out.println(new Date().toLocaleString());
    }
}

目标对象所在类的核心业务代码:

public class UserDaoImpl implements UserDao {

    @Override
    public void addUser() {

//        if (true) {
//            throw new RuntimeException("测试一下遇到异常执行情况");
//        }

        System.out.println("添加用户...");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}

Spring配置:

<?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"
       xmlns:context="http://www.springframework.org/schema/context"

       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd

           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
           ">

    <!-- 注册UserDao对象 -->
    <bean id="userDao" class="com.wtu.spring.aop.before.UserDaoImpl">
    </bean>

    <!-- 配置切面 -->
    <bean id="myLog" class="com.wtu.spring.aop.before.MyLog"/>

    <!--
        aop:config: 配置代理对象
        proxy-target-class: 指底层的动态代理使用的是JDK, 还是CGLIB代理;
            true表示CGLIB代理, CGLIB代理不是说没有接口, spring会根据目标对象自动生成一个接口
        aop:pointcut: 配置切入点
        execution: 确定切入点的方法
        aop:aspect: 配置切面如何去切切入点
        pointcut-ref: 切面中的方法去切哪个目标对象中的切入点
    -->
    <aop:config proxy-target-class="true">
        <aop:pointcut expression="execution(* com.wtu.spring.aop.before.UserDaoImpl.a*(..))" id="xxx"/>

        <aop:aspect ref="myLog">
            <aop:before method="writeConsole" pointcut-ref="xxx"/>
        </aop:aspect>
    </aop:config>

</beans>

测试类:

package com.wtu.spring.aop.before;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author menglanyingfei
 * @Created on 2018.01.17 14:22
 */
public class TestSpringIoc {
    public static void main(String[] args) {
        // 启动Ioc容器
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                new String[]{"com/wtu/spring/aop/before/spring3.0.xml"}
        );

        UserDao userDao = (UserDao) ac.getBean("userDao");
        userDao.addUser();

        System.out.println(userDao.getClass());
        //class com.wtu.spring.aop.before.UserDaoImpl$$EnhancerByCGLIB$$aa48c241
    }
}

运行结果:


运行结果.png

完整代码地址见Github

https://github.com/menglanyingfei/SSMLearning/tree/master/spring_day02

上一篇下一篇

猜你喜欢

热点阅读