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(); //通过代理完成租房
静态代理的弊端:
- 更换了核心业务后,需要重新创建代理类;代理数目过多,不宜维护
- 接口的核心功能改变后,其他代理类与目标类都需要跟着改变,维护性差
动态代理
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.通配切点可以配置哪些切点规则?
答:
匹配参数
匹配方法名(无参)
匹配方法名(任意参数)
匹配返回值类型
匹配类名
匹配包名
匹配包名、以及子包名