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
静态代理
需求:实现房东租房.
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)编程
面向切面编程的作用:就是将项目中与核心逻辑无关的代码横向抽取成切面类,通过织入作用到目标方法,以使目标方法执行前后达到增强的效果.
原理: 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; /** * @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.wss.service.UserServiceImpl"/> <!-- 目标类 --> <bean id="houseService" class="com.wss.service.HouseServiceImpl"/> <!-- 创建切面类对象 --> <bean id="myAspect" class="com.wss.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.wss.service.UserServiceImpl"/> <!-- 目标类 --> <bean id="houseService" class="com.wss.service.HouseServiceImpl"/> <!-- 创建切面类对象 --> <bean id="myAspect" class="com.wss.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.wss.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>