OCP开闭原则
OCP(Open-Closed Principle):开放封闭原则
OCP是面向对象设计中的重要原则之一,其核心思想是:软件实体(类、模块、函数等)应该'对扩展开放,对修改关闭'。
也就是说,如果在进行功能扩展的时候,添加额外的类是没有问题的,
但是因为功能扩展而修改之前运行正常的程序,就是忌讳的,不被允许的。
因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试,这是相当麻烦的过程。
导致这样问题的主要原因是:代码和代码之间的耦合度太高了。
依赖倒置原则
DIP(Dependence Inveersion Principle):依赖倒置原则
DIP倡导程序要面向抽象编程,面向接口编程,不要面向具体编程,不要依赖于具体事项。
让上层不再依赖下层,下面改动了,上面的代码不会受到牵连。
这样可以大大降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也会增强。
(软件七大开发原则都是在为解耦合服务)
思考
1.如何做到依赖倒置?
2.依赖又倒置给谁,谁来实现和维护?
Spring框架就很好的做到了这一点,在Spring框架中,spring帮我们new对象,将new出来的对象赋值到属性上,并且帮助我们维护对象和对象之间的的关系。
这一点就称之为控制反转IOC,将Bean的创建交给了第三方Spring,由Spring创建管理。
IoC控制反转
IOC
IOC(Inversion of Control):控制反转
上文提到的"依赖倒置"其实说的就是一种"控制反转"的思想。
IOC是面向对象编程中的一种设计思想,可以用来降低代码之间的耦合度,符合依赖倒置原则。
控制反转的核心是:
将对象的创建权交出去,将对象与对象之间关系的管理权交出去,由第三方容器来负责创建和维护。
而Spring框架就是一个实现了IOC思想的框架。
Spring Ioc容器的实现原理:工厂模式+解析XML+反射机制
依赖注入DI
DI(Dependency Injection):依赖注入
DI是Spring对控制反转的具体实现
通常,依赖注入的实现又包括两种方式:
● set方法注入
● 构造方法注入
Set方法注入
set注入基于set方法实现:
底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供set方法。
public class UserService {
private UserDao userDao;
// 使用set方式注入,必须提供set方法。
// 反射机制要调用这个方法给属性赋值的。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao" ref="userDaoBean"/>
</bean>
</beans>
构造注入
核心原理:通过调用构造方法来给属性赋值。
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="xxxx" class="com.powernode.spring6.dao.UserDao"/>
<bean id="yyyy" class="com.powernode.spring6.dao.VipDao"/>
<bean id="csBean3" class="com.powernode.spring6.service.CustomerService">
<!--不指定下标,也不指定参数名,让spring自己做类型匹配吧。-->
<!--这种方式实际上是根据类型进行注入的。spring会自动根据类型来判断把ref注入给哪个参数。-->
<constructor-arg ref="yyyy"/>
<constructor-arg ref="xxxx"/>
</bean>
<bean id="csBean2" class="com.powernode.spring6.service.CustomerService">
<!--根据构造方法参数的名字进行注入。-->
<constructor-arg name="vipDao" ref="yyyy"/>
<constructor-arg name="userDao" ref="xxxx"/>
</bean>
<bean id="csBean" class="com.powernode.spring6.service.CustomerService">
<!--构造注入-->
<!--
index属性指定参数下标,第一个参数是0,第二个参数是1,第三个参数是2,以此类推。
ref属性用来指定注入的bean的id
-->
<!--指定构造方法的第一个参数,下标是0-->
<constructor-arg index="0" ref="xxxx"/>
<!--指定构造方法的第二个参数,下标是1-->
<constructor-arg index="1" ref="yyyy"/>
</bean>
</beans>
set注入和构造注入有何不同?
set注入和构造注入的注入先后时机不同。
set注入和构造注入的注入时机不同:
1.set注入是通过对象的set方法,说明对象在此之前已经创建了;
2.构造注入是通过对象的构造方法注入,说明是创建对象的过程中就已经注入了
基于xml的自动装配
Spring还可以完成自动化的注入,自动化注入又被称为自动装配(基于set注入)。
它可以根据名字(byName)进行自动装配,也可以根据类型(byType)进行自动装配。
无论是byName还是byType,在装配的时候都是基于set方法的。所以set方法是必须要提供的。提供构造方法是不行的,
byName自动注入
<bean id="userService" class="com.powernode.spring6.service.UserService" autowire="byName"/>
<bean id="aaa" class="com.powernode.spring6.dao.UserDao"/>
byType自动注入
<!--byType表示根据类型自动装配-->
<bean id="accountService" class="com.powernode.spring6.service.AccountService" autowire="byType"/>
<bean class="com.powernode.spring6.dao.AccountDao"/>
IoC注解
注解的存在意义主要是为了简化XML的配置。Spring6倡导全注解开发。
我们来回顾一下:
● 第一:注解怎么定义,注解中的属性怎么定义?
● 第二:注解怎么使用?
● 第三:通过反射机制怎么读取注解?
元注解
什么是元注解?
用来标注“注解类型”的注解,称为元注解。
常见的元注解有哪些?
Target
Retention
关于Target注解
Target是一个元注解,用来标注“被标注的注解”可以出现在哪些位置上。
@Target(ElementType.METHOD):表示"被标注的注解"只能出现在方法上。
关于Retention注解
Retention是一个元注解,用来标注“被标注的注解”最终保存在哪里。
@Retention(RetentionPolicy.SOURCE):表示该注解只被留在Java源文件中。
@Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制读取。
关于@Deprecated注解
Deprecated这个注解标注的元素已过时。
这个注解主要向其它程序员传达一个信息,告知已过时,由更好的解决方案存在。
RententionPolicy源码就是一个枚举类
声明Bean的注解
负责声明Bean的注解,常见的包括四个
@Component
@Controller
@Service
@Repository
Comonent注解源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
Controller 源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
Service 源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
Repository 源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
总结
通过源码可以看到,@Controller、@Service、@Repository这三个注解都是@Component注解的别名。
也就是说:这四个注解的功能都一样。用哪个都可以。
只是为了增强程序的可读性,建议:
● 控制器类上使用:Controller
● service类上使用:Service
● dao类上使用:Repository
他们都是只有一个value属性。value属性用来指定bean的id,也就是bean的名字。
负责注入的注解
@Component @Controller @Service @Repository 这四个注解是用来声明Bean的,声明后这些Bean将被实例化。
实例化后如何给Bean的属性赋值。
给Bean属性赋值需要用到这些注解:
● @Value
● @Autowired
● @Qualifier
● @Resource
Value 注解源码
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
String value();
}
Autowired注解源码
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
Qualifier 注解源码
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
Resource 注解源码
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {
String name() default "";
String lookup() default "";
Class<?> type() default Object.class;
AuthenticationType authenticationType() default Resource.AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
public static enum AuthenticationType {
CONTAINER,
APPLICATION;
private AuthenticationType() {
}
}
}
Sping特点
Spring是一个无侵入式的针对Bean的生命周期进行管理的轻量级的开源框架。是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。
1. 轻量
a. 从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。
并且Spring所需的处理开销也是微不足道的。
b. Spring是非侵入式的:Spring应用中的对象不依赖于Spring的特定类。
2. 控制反转
a. Spring通过一种称作控制反转(IoC)的技术促进了松耦合。
当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
3. 面向切面
a. Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。
应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
4. 容器
a. Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,
你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),
你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。
然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
5. 框架
a. Spring可以将简单的组件配置、组合成为复杂的应用。
在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。
Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
Bean
被Spring容器管理起来的对象我们称之为Bean
问:Spring默认情况下是如何管理Bean的
我们先创建一个SpringBean的对象,将它纳入Spring容器管理
public class SpringBean {
public SpringBean() {
System.out.println("SpringBean的无参数构造方法执行了!");
}
}
spring-scope.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="sb" class="com.lei.beans.SpringBean"/>
</beans>
测试类
package com.lei;
import com.lei.beans.SpringBean;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringScopeTest {
@Test
public void scopeTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-scope.xml");
SpringBean sb = context.getBean("sb", SpringBean.class);
System.out.println(sb);
SpringBean sb2 = context.getBean("sb", SpringBean.class);
System.out.println(sb2);
SpringBean sb3 = context.getBean("sb", SpringBean.class);
System.out.println(sb3);
}
}
答:默认情况下Bean是单例的(singleton),在Spring上下文初始化的时候实例化,每次调用getBean()方法,都返回单例的对象。
修改Bean的作用域改为多例
当Spring的Bean的作用域修改为prototype时,Spring加载上下文时并不会调用构造方法初始化Bean
Bean的作用域不只单例和多例这两个,web应用时会有request、session等作用域,需要引用spring mvc依赖
scope属性的值不止两个,它一共包括8个选项:
● singleton:默认的,单例。
● prototype:原型。每调用一次getBean()方法则获取一个新的Bean对象。或每次注入的时候都是新对象。
● request:一个请求对应一个Bean。仅限于在WEB应用中使用。
● session:一个会话对应一个Bean。仅限于在WEB应用中使用。
● global session:portlet应用中专用的。如果在Servlet的WEB应用中使用global session的话,和session一个效果。
(portlet和servlet都是规范。servlet运行在servlet容器中,例如Tomcat。portlet运行在portlet容器中。)
● application:一个应用对应一个Bean。仅限于在WEB应用中使用。
● websocket:一个websocket生命周期对应一个Bean。仅限于在WEB应用中使用。
● 自定义scope:很少使用。
<!--引入web的框架,例如springmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.0-M2</version>
</dependency>
Bean的实例化方式
Spring为Bean提供了多种实例化方式,通常包括4种方式。(也就是说在Spring中为Bean对象的创建准备了多种方案,目的是:更加灵活)
● 第一种:通过构造方法实例化
● 第二种:通过简单工厂模式实例化
● 第三种:通过factory-bean实例化
● 第四种:通过FactoryBean接口实例化
第三种和第四种实例化的方式可以让我们对需要进行实例化的对象进行二次加工。
第一种,构造方法实例化
user
package com.lei.bean;
public class User {
public User() {
System.out.println("User的无参构造被调用了");
}
}
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="user" class="com.lei.bean.User"/>
</beans>
test
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserInstantiationTest {
@Test
public void instantiationTest1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user);
}
}
第二种,简单工厂模式实例化
Vip
public class Vip {
}
VipFactory
public class VipFactory {
public static Vip get(){
return new Vip();
}
}
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="vip" class="com.lei.bean.VipFactory" factory-method="get"/>
</beans>
测试
@Test
public void instantiationTest2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Vip vip = applicationContext.getBean("vip",Vip.class);
System.out.println(vip);
}
第三种:通过factory-bean实例化
order
package com.lei.bean;
public class Order {
}
orderFactory
package com.lei.factory;
import com.lei.bean.Order;
public class OrderFactory {
public Order get(){
return new Order();
}
}
spring.xml
<bean id="orderFactory" class="com.lei.factory.OrderFactory"/>
<bean id="orderBean" factory-bean="orderFactory" factory-method="get"/>
测试类
@Test
public void instantiationTest3(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Order order = applicationContext.getBean("orderBean",Order.class);
System.out.println(order);
}
第四种:通过FactoryBean接口实例化
第四种方式是三种方式的简化
以上的第三种方式中,factory-bean是我们自定义的,factory-method也是我们自己定义的。
在Spring中,当你编写的类直接实现FactoryBean接口之后,
factory-bean不需要指定了,factory-method也不需要指定了。
factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。
Persion
public class Person {
}
PersonFactory
package com.lei.factory;
import com.lei.bean.Person;
import org.springframework.beans.factory.FactoryBean;
public class PersonFactory implements FactoryBean {
@Override
public Person getObject() throws Exception {
return new Person();
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
// true 表示单例
// false 表示原型多例
return FactoryBean.super.isSingleton();
}
}
spring.xml
<bean id="personBean" class="com.lei.factory.PersonFactory"/>
测试
@Test
public void instantiationTest4(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Person person = applicationContext.getBean("personBean",Person.class);
System.out.println(person);
}
BeanFactory和FactoryBean的区别
BeanFactory:是工厂
BeanFactory是Spring IoC容器的顶级对象,BeanFactory被称为"Bean工厂",
在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。
FactoryBean:它是一个Bean,是一个能够辅助Spring实例化其它Bean对象的一个Bean。
在Spring中,Bean可以分为两类:
● 第一类:普通Bean
● 第二类:工厂Bean(记住:工厂Bean也是一种Bean,只不过这种Bean比较特殊,它可以辅助Spring实例化其它Bean对象。)
Bean的生命周期
生命周期之五步
Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法。
Bean生命周期可以粗略的划分为五大步:
● 第一步:实例化Bean
● 第二步:Bean属性赋值
● 第三步:初始化Bean
● 第四步:使用Bean
● 第五步:销毁Bean
User
package com.lei.bean;
public class User {
private String name;
public User() {
System.out.println("第一步:空参实例化Bean");
}
public void setName(String name) {
this.name = name;
System.out.println("第二步:Bean属性赋值");
}
// 这个方法需要自己写,自己配。方法名随意。
public void initBean(){
System.out.println("第三步:初始化Bean");
}
// 这个方法需要自己写,自己配。方法名随意。
public void destroyBean(){
System.out.println("第五步:销毁Bean");
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!--需要手动指定初始化方法,和销毁方法。-->
<bean id="userBean" class="com.lei.bean.User" init-method="initBean" destroy-method="destroyBean">
<property name="name" value="二狗子"/>
</bean>
</beans>
测试
public class BeanFifeCycleTest {
@Test
public void BeanFifeCycleFiveTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println("第四步:使用Bean");
System.out.println(userBean);
//只有正常关闭spring容器才会执行销毁方法
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext)applicationContext;
context.close();
}
}
需要注意的:
● 第一:只有正常关闭spring容器,bean的销毁方法才会被调用。
● 第二:ClassPathXmlApplicationContext类才有close()方法。
● 第三:配置文件中的init-method指定初始化方法。destroy-method指定销毁方法。
生命周期之七步
在以上的5步中,第3步是初始化Bean,如果你还想在初始化前和初始化后添加代码,可以加入“Bean后处理器”。
如果加上Bean后处理器的话,Bean的生命周期就是7步了:
编写一个LogBeanPostProcessor类实现BeanPostProcessor接口,并且重写before和after方法:
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器的before方法执行,即将开始初始化");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器的after方法执行,已完成初始化");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
spring.xml
<bean id="logBean" class="com.lei.bean.LogBeanPostProcessor"/>
一定要注意:在spring.xml文件中配置的Bean后处理器将作用于当前配置文件中所有的Bean。
代理模式
应用背景
西游记高老庄收猪八戒那一集中,猪八戒背着高小姐,背着背着怎么变成了背着孙猴子了。原来是孙悟空变成了高小姐,替高小姐和猪八戒成亲了。这其实就是一个代理模式:孙悟空代替了高小姐和猪八戒成亲,既保护了高小姐又完成了八戒非要和高小姐成亲的愿望。这就是非常典型的代理模式保护机制。
在成亲的过程中,八戒并不知道眼前的高小姐是孙悟空,而孙悟空却知道他是代替高小姐和猪八戒成亲的,孙悟空既了解高小姐,又知道猪八戒。
代理模式中有一个非常重要的特点:
对于客户端程序来说,使用代理对象时就像在使用目标对象一样。
代理模式中的角色
代理模式中的角色:
● 代理类:(代理主题)孙悟空
● 目标类:(真实主题)高小姐
● 代理类和目标类的公共接口(抽象主题):客户端(八戒)在使用代理类时就像在使用目标类,
不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。
代理模式的类图:
动态代理
动态代理:
程序运行阶段,在内存中动态生成代理类的方式叫做动态代理,目的是为了减少代理类的数量,解决代码复用的问题。
JDK动态代理
由JDK的java.lang.reflect包下的Proxy类的newProxyInstance()来实现,该动态代理只能代理接口。
newProxyInstance() 的三个参数
newProxyInstance源码
package java.lang.reflect;
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
@SuppressWarnings("removal")
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
newProxyInstance()是java.lang.reflect.Proxy中的方法
这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。
newProxyInstance 翻译为:新建代理对象
也就是说,通过调用这个方法可以创建代理对象。
本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事:
第一件事:在内存中动态的生成了一个代理类的字节码class。
第二件事:new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。
其中newProxyInstance()方法有三个参数:
● 第一个参数:"ClassLoader loader" 类加载器。
在内存中生成的字节码class文件,加载类就需要类加载器,执行代码得先加载到内存当中,所以这里需要指定类加载器。
并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。
● 第二个参数:"Class<?>[] interfaces" 接口类型。
代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
● 第三个参数:"InvocationHandler h" 调用处理器。
InvocationHandler 被翻译为:调用处理器,是一个接口。
这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。
在调用处理器接口中编写的就是:增强代码。具体需要增强什么还是得自己写的,不可能JDK也能猜到你需要增强什么帮你自动生成。
显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
JDK实现代理的步骤
第一步:创建目标对象
// 创建目标对象:也就是具体的业务实现类
OrderService target = new OrderServiceImpl();
第二步:创建代理对象
创建代理对象:TimerInvocationHandler(),该代理对象需要实现InvocationHandler接口
1. 为什么强行要求你必须实现InvocationHandler接口?
因为一个类实现接口就必须实现接口中的方法。
以下这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。
注意:invoke方法不是我们程序员负责调用的,是JDK负责调用的。
2. invoke方法什么时候被调用呢?
当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。
3. invoke方法的三个参数:
invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数。
我们可以在invoke方法的大括号中直接使用。
第一个参数:Object proxy 代理对象的引用。这个参数使用较少。
第二个参数:Method method 目标对象上的目标方法。(要执行的目标方法就是它。)
第三个参数:Object[] args 目标方法上的实参。
invoke方法执行过程中,使用method来调用目标对象的目标方法。
public class TimerInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
public TimerInvocationHandler(Object target) {
// 赋值给成员变量。
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这个接口的目的就是为了让你有地方写增强代码。
//System.out.println("增强1");
long begin = System.currentTimeMillis();
// 调用目标对象上的目标方法
// 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。
Object retValue = method.invoke(target, args);
//System.out.println("增强2");
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
// 注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
return retValue;
}
}
第三步:调用代理对象的代理方法
客户端程序
public class Client {
//客户端程序
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
// 上面代码通过一个工具类的封装,就简洁了。
OrderService proxyObj = (OrderService) ProxyUtil.newProxyInstance(target);
// 调用代理对象的代理方法
// 注意:调用代理对象的代理方法的时候,如果你要做增强的话,目标对象的目标方法得保证执行。
proxyObj.generate();
proxyObj.modify();
proxyObj.detail();
String name = proxyObj.getName();
System.out.println(name);
}
}
封装ProxyUtil工具类
public class ProxyUtil {
/**
* 封装一个工具方法,可以通过这个方法获取代理对象。
* @param target
* @return
*/
public static Object newProxyInstance(Object target){
// 底层是调用的还是JDK的动态代理。
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
}
}
CGlib动态代理
CGLIB(Code Generation Library)是一个第三方的开源项目。
是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。
CGLIB需要引入第三方依赖
<!--cglib依赖-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor
代理类
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 前面增强
long begin = System.currentTimeMillis();
// 怎么调用目标对象的目标方法呢?
Object retValue = methodProxy.invokeSuper(target, objects);
// 后面增强
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
return retValue;
}
}
客户端程序
public class Client {
public static void main(String[] args) {
// 创建字节码增强器对象
// 这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类。
Enhancer enhancer = new Enhancer();
// 告诉CGLIB父类是谁。告诉CGLIB目标类是谁。
enhancer.setSuperclass(UserService.class);
// 设置回调(等同于JDK动态代理当中的调用处理器。InvocationHandler)
// 在CGLIB当中不是InvocationHandler接口,是方法拦截器接口:MethodInterceptor
enhancer.setCallback(new TimerMethodInterceptor());
// 创建代理对象
// 这一步会做两件事:
// 第一件事:在内存中生成UserService类的子类,其实就是代理类的字节码。
// 第二件事:创建代理对象。
// 父类是UserService,子类这个代理类一定是UserService
UserService userServiceProxy = (UserService) enhancer.create();
// 建议大家能够把CGLIB动态代理生成的代理对象的名字格式有点印象。
// 根据这个名字可以推测框架底层是否使用了CGLIB动态代理
System.out.println(userServiceProxy);
// 调用代理对象的代理方法。
boolean success = userServiceProxy.login("admin", "123");
System.out.println(success ? "登录成功" : "登录失败");
userServiceProxy.logout();
}
}
Spring对事务的支持
Spring对事务的支持
1.编程式事务
通过编写代码的形式来实现事务的管理
2.声明式事务
基于注解方式
基于XML配置方式
Spring对事务管理的底层实现方式是基于AOP实现的,采用了AOP的方式进行了封装。所有Spring针对事务开发了一套API,PlatformTransactionManager接口
PlatformTransactionManager接口:spring事务管理器的核心接口。在Spring6中它有两个实现:
● DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。
● JtaTransactionManager:支持分布式事务管理。
如果要在Spring6中使用JdbcTemplate,就要使用DataSourceTransactionManager来管理事务。(Spring内置写好了,可以直接用。)
事务隔离级别
三大读问题
数据库中读取数据存在的三大问题:
1.脏读: 读取到没有提交到数控库的数据
2.不可重复读: 在同一个事务中,第一次和第二次读取的数据不一样;
3.幻读: 读到的数据是假的
事务隔离的四个级别
● 读未提交:READ_UNCOMMITTED
这种隔离级别,存在脏读问题,所谓的脏读(dirty read)表示能够读取到其它事务未提交的数据。
● 读已提交:READ_COMMITTED
解决了脏读问题,其它事务提交之后才能读到,但存在不可重复读问题。
● 可重复读:REPEATABLE_READ
解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的。但存在幻读问题。
● 序列化:SERIALIZABLE
解决了幻读问题,事务排队执行。不支持并发。
Spring中Transactional注解中Isolation的源码