回顾
Spring是一个轻量级J2EE框架.特点:支持IOC,AOP,相当于是容器,其他框架可以很好的跟Spring整合.
IOC: 控制反转.是将创建对象的控制权反转给Spring容器
DI: 依赖注入,其实就是属性赋值.
赋值可以赋值基本类型,引用类型,集合类型(了解)
<property name=-“属性名” value=“值”>
<property name=-“属性名” ref=“另个bean的id”>
DI注入的方式:
1)set方法(重要)
2)构造方法(了解)
注解开发:
创建对象的注解: @Service @Controller @Component @Repository
属性注入的注解: @Value @Autowired @Qualixxx(“对象名”)
注解生效
<context:component-scan base-package=“com.qf”>
代理模式
接下来学AOP,AOP的底层是动态代理技术,先学习代理技术.
代理单词: proxy
介绍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZUB45Rn8-1664195531012)(day56_spring.assets/image-20220926100639540.png)]
静态代理
需求:实现房东租房.
public interface FangDong {
void zufang();
}
public class FangdongImpl implements FangDong {
@Override
public void zufang() {
System.out.println("房东租房..." );
}
}
// 代理
public class FangdongProxy {
// 目标对象
private FangDong fangDong;
public FangdongProxy(FangDong fangDong){
this.fangDong = fangDong;
}
void zufang(){
// 前置增强
System.out.println("前:发传单,找客源" );
// 房东租房
fangDong.zufang();
// 后置增强
System.out.println("后:签合同" );
}
}
// 开始租房
public static void main(String[] args) {
// 找代理
FangdongProxy proxy = new FangdongProxy(new FangdongImpl( ));
// 真正租房
proxy.zufang();
}
现在这是静态代理,代理目的就是将目标方法增强.但是现在是静态代理.
什么是静态代理:
静态代理就是: 一个代理只能做一件事.做其他事情,需要再创建新代理.
目前是房东租房,创建了房东的代理,后续汽车厂要卖车,创建一个汽车的代理商,房东要卖房,找一个卖房的中介;对于代码而言,随着项目的扩展,功能变多,那么需要被代理的类多,那么对应于的代理类就会多.即需要不断根据需要创建代理.
上述问题,如果有一种功能,可以动态的根据情况创建代理.这就是动态代理技术.
动态代理
动态代理会动态的为目标类产生代理对象.动态代理有两种实现方案: 1) JDK动态代理 2) CGLIB动态代理.
1) JDK代理,但是只能代理接口.即目标类要有接口
1) CGLIB代理,可以代理接口,也可以代理类,目录类可以有接口,也可以没有接口
JDK的动态代理
JDK代理,是Java自有技术,无需导入依赖包.
需求: 使用动态代理技术,动态的产生房东代理对象,汽车厂商的代理
public class JDKProxy implements InvocationHandler {
// 目标类
private Object target;
// 指定目标对象
public JDKProxy(Object target){
this.target = target;
}
/**
* @param proxy 被代理对象
* @param method 目标方法
* @param args 目标方法的参数
* @return 目标方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 目标方法前:
System.out.println("前置增强" );
// 目标方法执行
Object obj = method.invoke(target,args);
System.out.println("后置增强" );
return obj;
}
}
public static void main(String[] args) {
// 目标类的类加载器
ClassLoader classLoader = FangdongImpl.class.getClassLoader( );
// 目标类的实现的所有接口
Class<?>[] interfaces = FangdongImpl.class.getInterfaces( );
// 目标方法执行处理器
JDKProxy handler = new JDKProxy(new FangdongImpl( ));
/**
* 参数1: 目标类的类加载器
* 参数2: 目标类的所实现的所有接口
* 参数3: 自己实现的目标方法处理器
*/
FangDong fangDong = (FangDong) Proxy.newProxyInstance(classLoader, interfaces, handler);
fangDong.zufang();
}
以上代码了解即可,不用记.
总结:
通过演示发现,有了动态代理技术,不管项目中有多少类,都可以为其产生代理对象.
结论:(掌握)
动态代理,会动态的产生目标类的代理对象,
JDK的动态代理,目标类必须有接口.
在实际开发中,AOP利用动态代理完成哪些事情?
- 日志记录
- 事务开启与提交
- 性能检测
- 权限校验
- 等等
CGLIB动态代理
CGLIB动态代理技术,又叫字节码增强.动态产生代理对象,可以代理接口也可以代理实现类.CGLIB是第三方技术,spring框架中已经整合了cglib的技术,所以只需导入spring-aop的依赖即可.
需求: 使用动态代理技术,动态的产生房东代理对象,汽车厂商的代理
// 代码不需要记
public class MyCglibInterceptor implements MethodInterceptor {
// cglib的增强器
private Enhancer enhancer = new Enhancer();
// 创建拦截器时,指定目标类的字节码
public MyCglibInterceptor(Class clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
}
// 提供一个获得代理对象的方法
public Object createProxyBean(){
return enhancer.create();
}
/**
* @param target 目标对象
* @param method
* @param args 目标方法的参数
* @param methodProxy 目标方法的代理对象
* @return 目标方法的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 目标方法前:
System.out.println("前置增强:权限校验,或者事务开启" );
// 目标方法执行
Object ret = methodProxy.invokeSuper(target,args);
// 目标方法前:
System.out.println("后置增强:日志记录,或者事务提交" );
return ret;
}
}
public static void main(String[] args) {
// 此处,只需要将其他类字节码文件传入此处
// 即可得到其他类的代理对象
MyCglibInterceptor interceptor = new MyCglibInterceptor(CarFactoryImpl.class);
CarFactoryImpl carFactory = (CarFactoryImpl) interceptor.createProxyBean( );
carFactory.saleCar();
}
总结:
CGLIB动态代理,会动态的给类产生代理对象.
目标类可以没有接口,也可以有接口,都可以代理.
总结:[重点]
代理的目的: 将目标方法功能增强.
代理的方式: 静态代理和动态代理.
什么区别:
静态代理: 每个类都需要自己创建代理对象,一个代理只能代理一个类
动态代理: 给每个类动态产生代理对象.(按需产生)
动态代理如何实现:
jdk的动态代理: jdk动态代理只能代理接口(目标类必须有接口)
cglib的动态代理:可以代理接口,也可以代理类(目标类可以有接口,也可以没接口)
思考: Mybatis获得Mapper就是动态代理技术.
AOP
OOP面向对象编程
AOP面向切面(Aspect)编程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FsRW5jgU-1664195531014)(day56_spring.assets/image-20220926145954978.png)]
面向切面编程的作用:就是将项目中与核心逻辑无关的代码横向抽取成切面类,通过织入作用到目标方法,以使目标方法执行前后达到增强的效果.
原理: AOP底层使用的就是动态代理,给AOP指定哪些类型需要增强,就会产生对应的代理对象,代理对象执行方法前后会先执行增强的方法.
好处:
抽取代码,复用,提供效率
减少耦合
利于代码扩展
AOP的术语:
目标类(Target): 被代理的类
连接点(JoinPoint): 目标类中准备被切入的方法
切入点(Pointcut): 真正执行的目标方法
切面(Aspect) : 切面中定义中增强的方法
增强(Advice): 也叫通知,就是在目标方法执行前/后的方法
织入(Weaving): 将增强作用到切入点的过程
AOP编程[重点]
- 依赖spring-aop.jar
- 创建所需UserService和UserServiceImpl
- 创建切面类
- 配置文件配置切面
- 测试
入门演示:环绕通知
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
接口和实现类
public interface UserService {
void addUser();
void deleteUserById();
}
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("UserServiceImpl.addUser 执行" );
}
@Override
public void deleteUserById() {
System.out.println("UserServiceImpl.deleteUserById 执行" );
}
}
切面类
import org.aspectj.lang.ProceedingJoinPoint;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 切面类
* 切面类中定义各种增强的方法
*/
public class MyAspect {
/**
* 增强的方法: 环绕通知
* 参数ProceedingJoinPoint: 目标方法
* @return
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 目标方法前
System.out.println("前置增强: 开启事务" );
// 目标方法执行
Object ret = joinPoint.proceed( );
// 目标方法后
System.out.println("后置增强: 提交事务" );
return ret;
}
}
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"
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">
<beans>
<!-- 目标类 -->
<bean id="userService" class="com.qf.service.UserServiceImpl"/>
<!-- 目标类 -->
<bean id="houseService" class="com.qf.service.HouseServiceImpl"/>
<!-- 创建切面类对象 -->
<bean id="myAspect" class="com.qf.aspect.MyAspect"/>
<!-- 织入 -->
<aop:config>
<!-- 配置切面,引用自定义切面对象 -->
<aop:aspect ref="myAspect">
<!-- 下面要做的是将通知作用到目标方法上 -->
<!-- 配置环绕通知 -->
<!--
method: 切面类中的方法名
pointcut: 切入点,内写切入点表达式
execution(* com.qf.service.*.*(..))
第一个*, 返回值的意思,*的意思是返回值任意
com.qf.service.* , 通过路径确定切入点所在类,*的意思是service包下所有类
.* ,是方法名,*是指所有方法
() 方法的参数列表
.. 方法内的任意参数
-->
<aop:around method="around" pointcut="execution(* com.qf.service.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
测试
public class TestAOP {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// UserService userService = context.getBean("userService", UserService.class);
HouseService houseService = context.getBean("houseService", HouseService.class);
// 目标方法
// userService.addUser();
// userService.deleteUserById();
houseService.addHouse();
}
}
AOP的动态代理
AOP的动态代理,底层使用了两种代理方案:
默认情况下使用JDK动态代理.即目标类必须有接口,JDK代理接口.当目标类没有实现接口时自动切换使用CGLIB实现动态代理.
其他通知方式
还有其他通知(增强)方式
- 前置增强(通知)
- 场景:一般用来做权限校验
- 后置增强(通知)
- 场景: 释放资源,或者记录日志
- 环绕增强(通知)
- 场景:数据库事务
- 后置返回增强(通知)
- 场景:得到目标方法返回值再处理
- 异常增强(通知)
- 场景:可以获得目标方法的异常信息,用于记录日志,或者进行异常拦截
切面类
public class MyAspect {
/**
* 前置增强
* 参数: JoinPoint 目标类对象
*/
public void before(JoinPoint joinPoint){
Object target = joinPoint.getTarget( );
System.out.println("前置增强,获得目标类对象"+target );
Signature signature = joinPoint.getSignature( );
System.out.println("前置增强,获得目标方法"+signature);
String name = signature.getName( );
System.out.println("前置增强,获得目标方法名"+name);
System.out.println("前置增强: 权限校验" );
}
/**
* 后置通知
*/
public void after(){
System.out.println("后置通知:记录执行的日志" );
// 应用场景: 还可以做一些关流,释放资源的动作
}
/**
* 增强的方法: 环绕通知
* 参数ProceedingJoinPoint: 目标方法
* @return
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 目标方法前
System.out.println("环绕-前置增强: 开启事务" );
// 目标方法执行
Object ret = joinPoint.proceed( );
System.out.println("环绕-获得目标方法返回值: "+ ret );
// 目标方法后
System.out.println("环绕-后置增强: 提交事务" );
return ret;
}
/**
* 后置返回通知
* 后置返回能得到目标方法的返回值
*/
public Object afterReturn(Object ret){
System.out.println("后置返回通知: "+ ret );
return ret;
}
/**
* 异常通知
*/
public void myThrow(Exception e){
// 可以接收到目标方法执行的异常信息
System.out.println("异常通知,获得异常信息"+e.getMessage() );
// 可以做全局异常处理,记录异常信息到日志
}
}
<?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"
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">
<!-- 目标类 -->
<bean id="userService" class="com.qf.service.UserServiceImpl"/>
<!-- 目标类 -->
<bean id="houseService" class="com.qf.service.HouseServiceImpl"/>
<!-- 创建切面类对象 -->
<bean id="myAspect" class="com.qf.aspect.MyAspect"/>
<!-- 织入 -->
<aop:config>
<!-- 配置切面,引用自定义切面对象 -->
<aop:aspect ref="myAspect">
<!-- 下面要做的是将通知作用到目标方法上 -->
<!-- 配置环绕通知 -->
<!--
method: 切面类中的方法名
pointcut: 切入点,内写切入点表达式
execution(* com.qf.service.*.*(..))
第一个*, 返回值的意思,*的意思是返回值任意
com.qf.service.* , 通过路径确定切入点所在类,*的意思是service包下所有类
.* ,是方法名,*是指所有方法
() 方法的参数列表
.. 方法内的任意参数
-->
<aop:around method="around" pointcut-ref="pointcut"/>
<!-- 将切入点表达式抽取,以便复用 -->
<aop:pointcut id="pointcut" expression="execution(* com.qf.service.*.*(..))"/>
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 后置通知 -->
<aop:after method="after" pointcut-ref="pointcut"/>
<!--后置返回
returning指定增强方法的参数名ret
-->
<aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="ret"/>
<!-- 异常通知
throwing指定增强方法的参数名e
-->
<aop:after-throwing method="myThrow" pointcut-ref="pointcut" throwing="e"/>
</aop:aspect>
</aop:config>
</beans>
作业
整理笔记
AOP编程代码要会
关于上午代理模式,需要记住结论,面试
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 后置通知 -->
<aop:after method="after" pointcut-ref="pointcut"/>
<!--后置返回
returning指定增强方法的参数名ret
-->
<aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="ret"/>
<!-- 异常通知
throwing指定增强方法的参数名e
-->
<aop:after-throwing method="myThrow" pointcut-ref="pointcut" throwing="e"/>
</aop:aspect>
</aop:config>
```
作业
整理笔记
AOP编程代码要会
关于上午代理模式,需要记住结论,面试