AOP编程

一、DI依赖注入

昨天讲解到SET注入,除了Set注入还有其他的构造注入,自建注入等方式

自建类型注入

就是IOC控制反转后,引用类型的属性值继续进行SET注入

<bean id="user" class="com.qf.a_entity.User">
    <!-- 自建类型的注入 -->
    <property name="addr" ref="ad"></property>
</bean>

<bean id="ad" class="com.qf.a_entity.Addr">
    <property name="id" value="1"></property>
    <property name="name" value="江西"></property>
</bean>

构造注入

通过构造方法的方式,给属性赋值;且构造方法中所有参数都需要填充值。

@Data
@AllArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
}
<!-- 构造注入 -->
<bean id="st" class="com.qf.a_entity.Student">
    <constructor-arg name="id" value="3"></constructor-arg>
    <constructor-arg name="name" value="zs"></constructor-arg>
    <constructor-arg name="sex" value="男"></constructor-arg>
    <constructor-arg name="age" value="30"></constructor-arg>
</bean>

自动注入

可以省略SET注入的方式,通过指定某种标记自动注入值(与IOC有关-注入bean对象)

<bean id="ud" class="com.qf.b_service.UserDaoImpl"></bean>
<bean id="userDao" class="com.qf.b_service.UserDaoImpl"></bean>
<!-- autowire="byType":自动注入  byType根据类型匹配业务层的userDao属性
         autowire="byName": 自动注入  匹配属性名  id与属性名一致
    -->
<bean id="userService" class="com.qf.b_service.UserServiceImpl" autowire="byName">
    <!--<property name="userDao" ref="ud"></property>-->
</bean>

二、Bean的细节

单例与多例

Spring容器中的bean默认为单例。可以在容器中改为多例

<bean id="userService" class="com.qf.b_service.UserServiceImpl" autowire="byName" scope="prototype">
    <!--<property name="userDao" ref="ud"></property>-->
</bean>
//测试单例与多例
UserService us2 = (UserService) context.getBean("userService");
System.out.println(userService==us2);  //默认为单例

//说明:spring中的bean默认为单例,在后续的应用中,往往有些情况需要多例,有些需要单例
//单例:SqlSessionFactory的bean对象,service对象,dao对象
//多例:SqlSession、Connection

FactoryBean扩展

FactoryBean在Spring的容器中,是一个Factory的bean对象;这种bean对象,我们叫做复杂的bean对象(了解);如何获取到复杂工厂中产生的bean对象? 需要实现FactoryBean接口

创建类实现FactoryBean接口

public class MyFactoryBean implements FactoryBean {
    @Override //从工厂中取出bean对象,该对象就是在spring容器中需要取出的复杂的bean对象
    public Object getObject() throws Exception {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql:///mydb1?serverTimezone=UTC","root","123");
        return conn;
    }
    @Override //返回反射对象
    public Class<?> getObjectType() {
        return Connection.class;
    }
    @Override  //判断是否为单例
    public boolean isSingleton() {
        return false;
    }
}

Spring容器中的代码:

<bean id="myf" class="com.qf.c_factory.MyFactoryBean"></bean>

测试类中的代码

//获取的bean,由FactoryBean中返回的对象决定
Connection conn = (Connection) context.getBean("myf");
System.out.println(conn);

//如果需要拿到FactoryBean本身的类型,则需要&myf
MyFactoryBean bean = (MyFactoryBean) context.getBean("&myf");
System.out.println(bean);

三、工厂特性

生命周期

工厂中的生命周期,主要讲解bean对象的生命周期;从创建,初始化,赋值,销毁等阶段的触发。

<bean id="ad" class="com.qf.a_entity.Addr" init-method="init" destroy-method="destroy">
    <property name="id" value="1"></property>
    <property name="name" value="江西"></property>
</bean>

在实体类中配置相关触发方法:

@Data
public class Addr {
    private  Integer id;
    private  String  name;

    public Addr(){
        System.out.println("构造方法...");
    }
    public void setName(String name){
        this.name = name;
        System.out.println("set方法的触发");
    }
    public void init(){  //初始化方法
        System.out.println("初始化方法...");
    }

    public void destroy(){  //销毁方法
        System.out.println("销毁方法...");
    }
}

测试:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
context.close();  //单例时,关闭容器则会销毁bean对象

细节

生命周期从单例和多例的角度进行测试

单例(默认):加载spring容器时,即可创建容器中的单例bean;关闭容器则会销毁bean对象

多例:加载Spring容器时,不会创建容器中的多例bean,需要getBean时才创建;关闭容器时,不会销毁bean对象

生命周期的执行顺序:

构造–>set方法–>初始化—>销毁(单例时关闭容器即销毁; 多例JVM关闭时才销毁)

四、代理设计模式

概述

将某一个核心类的辅助功能抽取出去,交给代理来完成;这样达到的效果是,核心功能更纯粹;辅助功能可复用; 例如:事务-辅助功能; 核心功能是CRUD,可将事务抽取交给代理完成。

静态代理

案例:租房业务完成

分析:租房接口、租房实现类、代理实现类实现租房接口

租房的接口:

public interface ZuFangService {
    public void zuFang();
}

核心业务的实现类:

public class ZuFangServiceImpl implements ZuFangService {
    @Override
    public void zuFang() {
        System.out.println("收房租,签合同"); //核心功能
    }
}

静态代理实现类:

public class StaticProxyService implements ZuFangService {
    private ZuFangService target = new ZuFangServiceImpl(); //核心的目标对象
    @Override
    public void zuFang() {
        System.out.println("待人看房..");     //辅助功能-交给代理完成

        target.zuFang();  //接口回调,回调到核心业务功能

        System.out.println("后续维修");      //辅助功能
    }
}

测试:

ZuFangService proxy = new StaticProxyService();
proxy.zuFang();  //通过代理完成租房

静态代理的弊端:

  1. 更换了核心业务后,需要重新创建代理类;代理数目过多,不宜维护
  2. 接口的核心功能改变后,其他代理类与目标类都需要跟着改变,维护性差

动态代理

Spring的动态代理有两种方式:JDK方式(基于接口)和CGLib方式(基于类)

JDK动态代理:(基于接口)

ZuFangService target = new ZuFangServiceImpl(); //创建目标对象
InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("看房...");  //辅助业务

        proxy = method.invoke(target,args);   //调用核心功能

        System.out.println("维修...");  //辅助业务
        return proxy;
    }
};
//使用Proxy创建代理对象--返回代理对象
//参数1:目标类的加载器,参数2:目标类的接口 参数3:处理器
ZuFangService proxy = (ZuFangService) Proxy.newProxyInstance(target.getClass().getClassLoader()                                                        ,target.getClass().getInterfaces(),handler);
proxy.zuFang(); //使用代理调用租房的方法

CGLib动态代理:(基于类)

public static void main(String[] args) {
    ZuFangService target = new ZuFangServiceImpl(); //目标对象
    Enhancer enhancer = new Enhancer();  //创建增强对象
    enhancer.setSuperclass(target.getClass()); //将目标对象当成父类
    //回调方法,传入处理器对象
    enhancer.setCallback(new InvocationHandler() {
        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            System.out.println("看房2");  //辅助功能
            method.invoke(target,objects); //核心功能中租房调用
            System.out.println("维修2");  //辅助功能
            return null;
        }
    });
    ZuFangService proxy = (ZuFangService) enhancer.create(); //创建出代理对象
    proxy.zuFang();
}

五、AOP编程

概述

AOP-面向切面编程,是一种"横切"技术,可以将辅助业务织入到核心业务中,使得辅助业务可灵活的织入到各个核心业务中,功能完整;且结构为解耦合方式。

AOP术语

  • 连接点(Joinpoint):核心业务类中的所有方法

  • 切点(Pointcut):可放入增强的连接点(关键点)

  • 通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。(关键点)

  • 目标对象(Target):核心类的对象

  • 织入(Weaving):将增强放入到切点的过程

  • 代理(Proxy):调用增强并织入到切点的对象

  • 切面(Aspect):将增强放入到切点的最终结果(关键点)

AOP配置

导入坐标依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

增加aop容器配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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
       ">

先配置好UserSevice的容器bean的获取

public class UserServiceImpl implements UserService {
    @Override
    public void add() {  //连接点
        //前置功能
        System.out.println("添加....");
    }
    @Override
    public void del() {
        System.out.println("删除....");
    }
}

添加增强类

//前置增强类--实现前置增强的接口
public class BeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("前置增强...");
    }
}

AOP的配置

<!-- 产生增强的bean -->
<bean id="ad" class="com.qf.f_advice.BeforeAdvice"></bean>

<bean id="userService" class="com.qf.e_aop.UserServiceImpl"></bean>

<!-- aop配置 -->
<aop:config>
    <!-- 添加切点的标签,id="point":标记名
         expression="execution(): 用于设置哪些连接点作为切点
         * add(): *代表返回值类型任意  方法名为add 无参
         -->
    <aop:pointcut id="point" expression="execution(* add())"/>
    <!-- 将切点与增强绑定,最终形成切面 -->
    <aop:advisor advice-ref="ad" pointcut-ref="point"></aop:advisor>
</aop:config>

增强类

前面配置类前置的增强类,还有后置增强,环绕增强,异常增强等。

增强类

public class AfterAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        //没有异常才执行此后置增强
        System.out.println("后置增强...");  //核心功能中有异常,则不会后置增强
    }
}
public class AfterThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Exception ex){
        System.out.println("后置异常增强");  //有异常才提示
    }
}
public class RondAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //环绕增强:前置增强+后置增强
        System.out.println("环绕前置...");
        invocation.proceed();
        System.out.println("环绕后置...");
        return null;
    }
}

将增强bean与切点绑定

<aop:pointcut id="point" expression="execution(* add())"/> 
<!-- 将切点与增强绑定,最终形成切面 -->
<aop:advisor advice-ref="after" pointcut-ref="point"></aop:advisor>
<aop:advisor advice-ref="throw" pointcut-ref="point"></aop:advisor>
<aop:advisor advice-ref="round" pointcut-ref="point"></aop:advisor>

通配切点

指定好通配切点的名字,参数,返回值;可以给对应的切点加增强

<!--返回值类型为User -->
<!--    <aop:pointcut id="point" expression="execution(com.qf.a_entity.User add(..))"/>-->
<!-- 匹配参数为User -->
<!--<aop:pointcut id="point" expression="execution(* add(com.qf.a_entity.User))"/>-->
<!-- 匹配全限定名下的方法 -->
<!--<aop:pointcut id="point" expression="execution(* com.qf.e_aop.UserServiceImpl.add())"/>-->
<!-- 匹配指定业务层的所有方法 -->
<aop:pointcut id="point" expression="execution(* com.qf.e_aop.UserServiceImpl.*())"/>

AOP中动态代理的选择:JDK还是CGLib

规则:如果有接口,则优先选择JDK动态代理;没有接口,则使用CGLib (搜索类快捷键:ctrl+shift+n)

六、总结与作业

总结

1.DI依赖注入(重点)
自建类型注入-IOC、构造注入,自动注入-类型,属性
2.Bean细节
单例与多例bean,FactoryBean扩展-复杂的bean
3.工厂特性
生命周期-构造,set,初始化,销毁;细节-单例和多例生命周期测试
4.代理设计模式(重点)
概述-辅助业务分离;静态代理-完成辅助功能; 
动态代理-代理对象灵活变更,两种代理:jdk与cglib
5.AOP编程(重点)
概述-切面;AOP术语:连接点,切点,目标对象,增强,代理,切面等
AOP配置:spring工厂+增强bean+aop配置(切点与增强绑定)
增强类、通配切点

作业

1.完成装修业务的静态代理,目标核心业务完成装修,代理类完成装修的购买材料;
2.完成装修的动态代理
3.给ProductService的增删改查功能增加前置功能

晨考

1.bean生命周期,单例和多例有什么区别
2.静态代理的弊端
3.JDK和CGLib的动态代理如何选择
4.通配切点可以配置哪些切点规则?
1.bean生命周期,单例和多例有什么区别
答:单例:加载容器时创建所有bean;关闭容器时,bean对象也随之销毁
多例:加载容器时不会创建bean;关闭容器时,bean对象也不会销毁
2.静态代理的弊端
答:1. 更换了核心业务后,需要重新创建代理类;代理数目过多,不宜维护
2. 接口的核心功能改变后,其他代理类与目标类都需要随之改变,维护性差
3.JDK和CGLib的动态代理如何选择
答:有接口时选择JDK动态代理
没有接口时选择CGLib动态代理
4.通配切点可以配置哪些切点规则?
答:
匹配参数
匹配方法名(无参)
匹配方法名(任意参数)
匹配返回值类型
匹配类名
匹配包名
匹配包名、以及子包名