2023-12-22
1、Java原生开发存在问题
1.1、DAO层存在问题
1)每次都要做大量的相同的操作
2)对执行sql语句过程中所出现的各种异常和资源释放进行处理
3)需要更多的代码来提取结果并将它们映射到对象实例中
1.2、Service层存在问题
1)当功能越来越多, 与业务逻辑无关, 但是被多处业务逻辑模块共享的代码
(比如判断用户登录, 日志管理, 权限检查, 事务管理等)
的修改和维护的成本大大增加。
2)没有做到资源复用,创建了许多重复对象。
造成大量资源浪费*
3)更换实现类需要改动多个地方。
修改和维护的成本大大增加。
参考CSDN博主「灵魂相契的树」的原创文章,原文链接:https://blog.csdn.net/yzhcjl_/article/details/132520053
参考知乎作者「码农出击」的原创文章,原文链接:https://zhuanlan.zhihu.com/p/493973685
1.3、Controller层存在问题
1)每个servlet处理特定的请求,如果servlet过多,会导致在web.xml内容过多(web.xml文件方式配置)、servlet类文件数量过多。
2)通常使用JSP进行页面展示,对前端不是特别友好。
3)servlet具有容器依赖性,必须放在服务器中运行,不利于单元测试。
JSP 页面无法直接访问需要有 Java Servlet 容器来编译和运行
2、解决方案
SSM框架开发.pngSpringMVC:
1)
2)
Spring:
1)Spring IOC有许多优点,它可以帮助改善代码的结构、可维护性和可测试性。
低耦合——代码分离、便于代码复用
2)Spring AOP能帮助我们无耦合的实现日志记录,性能统计,安全控制等。
高内聚——结构清晰、便于开发维护
Mybatis:
1)避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。resultType自动映射查询结果,resultMap复杂场景下构建一个结果映射
参考CSDN博主「向上爬的小蜗牛」的原创文章,原文链接:https://blog.csdn.net/LyySwx/article/details/90900013
3、Spring
3.1、IOC
IoC(inversion of control):把对象的创建、赋值、管理工作都交给代码之外的容器实现,即对象的创建是由其他外部资源完成。
相关概念
1)控制:创建对象,对象的属性赋值,对象之间的关系管理。
2)反转:把原来的开发人员管理、创建对象的权限转移给代码之外的容器实现。由容器代替开发人员管理对象、创建对象、给属性赋值。
3)正转:由开发人员在代码中,使用new构造方法创建对象等,开发人员主动管理对象。
/*
示例:
Spring IoC 容器创建好 B 的实例对象后并赋值给 A 对象中的 b 属性(成员变量)的过程,就是所谓的「依赖注入
*/
public class A {
private B b;
// 省略 getter 和 setter
// 省略构造方法
}
IoC的技术实现:
DI (Dependency Injection,依赖注入):是IoC 的一种具体实现方式,它是指将对象所依赖的其他对象(即依赖)通过构造函数、Setter 方法或其他方式注入到对象中,从而消除了对象之间的耦合关系。
只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中创建、赋值、查找都由容器内部实现
3.1.1、依赖注入的方式
1)基于构造方法的依赖注入
构造方法的优势: 保证一些必要的属性在Bean实例化时就得到设置,并且确保了Bean实例在实例化后就可以使用。
构造方法的弊端: 在创建对象时,如果用不到这些数据,也必须提供。
标签:<constructor-arg></constructor-arg>
属性:
type:用于指定要注入的数据类型,该数据类型也是构造函数中的某些类型或者某些参数
index:指定要注入的数据给构造函数中指定索引位置的参数赋值,参数索引的位置从0开始
name:用于指定构造函数中指定名称的参数赋值(常用)
(以上三个标签根据需要选一个就行)
一一一一一一一一一一一一一一一一一
value:用于提供基本类型和String类型的数据
ref:用于指定其他Bean类型的数据,需填写在SpringIOC容器中的唯一id
- Dao类
//基于构造方法注入
public class UserDao {
private String name;
private int age;
public UserDao(String name, int age) {
this.name = name;
this.age = age;
}
public void insert(){
System.out.println("保存用户:"+age+"岁"+name);
}
}
- Service类
//基于构造方法注入
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void add(){
userDao.insert();
}
}
-
XML配置文件
Ⅰ、按名称匹配入参
<!--按名称匹配入参-->
<bean id="userDao" class="org.example.dao.UserDao">
<constructor-arg name="name" value="李四"></constructor-arg>
<constructor-arg name="age" value="55"></constructor-arg>
</bean>
<bean id="userService" class="org.example.service.UserService">
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
Ⅱ、按类型匹配入参
<!--按类型匹配入参-->
<bean id="userDao" class="org.example.dao.UserDao">
<constructor-arg type="java.lang.String" value="李四"></constructor-arg>
<constructor-arg type="int" value="55"></constructor-arg>
</bean>
<bean id="userService" class="org.example.service.UserService">
<constructor-arg type="org.example.dao.UserDao" ref="userDao"></constructor-arg>
</bean>
Ⅲ、按索引匹配入参
<!--按索引匹配入参-->
<bean id="userDao" class="org.example.dao.UserDao">
<constructor-arg index="0" value="李四"></constructor-arg>
<constructor-arg index="1" value="55"></constructor-arg>
</bean>
<bean id="userService" class="org.example.service.UserService">
<constructor-arg index="0" ref="userDao"></constructor-arg>
</bean>
Ⅳ、联合使用类型和索引匹配入参(适用于有多个构造函数,根据索引入参无法匹配的情况)
<!--联合使用类型和索引匹配入参-->
<bean id="userDao" class="org.example.dao.UserDao">
<constructor-arg index="0" type="java.lang.String" value="李四"></constructor-arg>
<constructor-arg index="1" type="int" value="55"></constructor-arg>
</bean>
<bean id="userService" class="org.example.service.UserService">
<constructor-arg index="0" type="org.example.dao.UserDao" ref="userDao"></constructor-arg>
</bean>
- 测试类
//基于构造方法注入
@Test
public void test(){
//读取配置文件
ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
//获取bean的实例
UserService userService =(UserService) ctx.getBean("userService");
//调用方法
userService.add();
}
-
运行结果
2)基于 Setter 的依赖注入
set方法的优势: 创建对象时没有明确的限制,可以直接使用默认构造函数,具有可选择性和灵活性高的优点。
set方法的弊端: 如果有某个成员必须有值的时候,则获取对象时有可能set方法无法保证一定注入。
标签:<property></property>
属性:
name:用于指定注入时调用的set方法名
value:用于提供基本类型和String类型的数据
ref:用于指定其他Bean类型的数据,需填写在SpringIOC容器中的唯一id
- Dao类
//基于set方法注入
public class UserDao {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void insert(){
System.out.println("保存用户:"+age+"岁"+name);
}
}
- Service类
//基于setter方法注入
public class UserService {
private UserDao userDao;
public void add(){
userDao.insert();
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
- XML配置文件
<!--基于setter方法注入-->
<bean id="userDao" class="org.example.dao.UserDao">
<property name="name" value="张三"></property>
<property name="age" value="15"></property>
</bean>
<bean id="userService" class="org.example.service.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
public class UserServiceTest {
//基于setter方法注入
@Test
public void test(){
//读取配置文件
ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
//获取bean的实例
UserService userService =(UserService) ctx.getBean("userService");
//调用方法
userService.add();
}
}
-
运行结果
3)基于 注解 的依赖注入
使用注解注入依赖对象不用再在代码中写依赖对象的setter方法或者该类的构造方法,并且不用再配置文件中配置大量的依赖对象,使代码更加简洁,清晰,易于维护。
- Dao类
//基于注解
@Repository
public class UserDao {
@Value("王五")
private String name;
@Value("15")
private int age;
public void insert(){
System.out.println("保存用户:"+age+"岁"+name);
}
}
//等价于XML配置中的
<bean id="userDao" class="org.example.dao.UserDao">
<constructor-arg name="name" value="王五"></constructor-arg>
<constructor-arg name="age" value="15"></constructor-arg>
</bean>
- Service类
@Service
public class UserService {
// @Autowired
// private UserDao userDao;
// @Autowired
// @Qualifier("userDao")
// private UserDao userDao;
@Resource(name = "userDao")
private UserDao userDao;
public void add(){
userDao.insert();
}
}
//等价于XML配置中的
<bean id="userService" class="org.example.service.UserService">
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
以上三种写法都可
- 测试类
//基于注解
@Autowired
private UserService userService;
@Test
public void test1(){
userService.add();
}
-
运行结果
Spring中常见注解
用于创建对象的注解
注解的作用和 xml 配置文件中编写 <bean></bean> 标签的功能是一样的,即将当前类对象存入 Spring 容器中
- ①
@Componcnt
注解:该注解用于把当前类对象存入 Spring 容器中。
该注解的属性:
value: 指定 bean 的 id。假如我们没有指定 value 的值,默认为当前类名的首字母改小写,其余不变的名称。 - ②其中 Spring 还为我们提供了三个作用和 @Component 一样的注解(使得我们的三层对象更加清晰):
@Controller
一般用在表现层
@Service
一般用在业务层
@Repository
一般用在持久层
用于改变创建的对象的注入数据的注解
- ①
@Autowritred
注解:该注解可以自动按照类型注入数据到 bean 中。基于暴力反射原理,无需提供setter等方法
注入条件:
如果 ioc 容器中用唯一的一个 bean 对象类型和要被注入的变量类型匹配
如果 ioc 容器中没有对应的 bean 对象类型和要被注入的变量类型匹配,那么会抛出异常。
如果 ioc 容器中有多个 bean 对象类型和要被注入的变量类型匹配,首先会根据 id 来匹配,如果 id 都一样,则会根据要被注入的变量的名称匹配,如果变量的名称都一样,那么就会抛出异常。 - ②
@Qualifier
注解:该注解,在 @Autowritred 的基础之上,可以添加 value 属性值,指定注入的 bean id(需要和 @Autowritred 一起使用)。 - ③
@Resource
该注解和 @Qualifier 类似,可以指定注入的 bean id,但不需要和 @Autowritred 一起使用。@Resource 单独使用。 - ④
@Value
注解:该注解用于注入基本数据类型和 String 数据类型
该注解的属性:
value:用于指定数据的值。可以是 Spring 中的 el 表达式(SpEL)
SpEL 书写格式:@Value("${xxxx}")
说明:
@Autowritred @Qualifier @Resource 只能注入其他 bean 类型的数据,不能注入基本数据类型和 String 类型的数据。
集合类型的数据只能通过 XML 文件进行注入。
用于改变创建的对象的作用范围的注解
- ①
@Scope
注解:该注解用于指定 Bean 的作用范围
该注解的属性:
value:指定范围的取值。吃常用的取值有:singleton(单例的)、prototype(多例的)
默认为单例的(对象只创建一次)
生命周期相关的注解
- ①
@PreDestroy
销毁方法的注解 - ②
@PostConstruct
初始化方法的注解
Spring的新注解
- ①
@Configuration
注解: 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解。 获取容器时需要使用AnnotationApplicationContext(有@Configuration 注解的类.class)。
// 如果加载spring-context.xml文件:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
// @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
- ②
@ComponentScan
注解: 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:<context:component-scan base-package="包扫描的路径"/> 是一样的
该注解的属性:
basePackages:用于指定要扫描的包。
value:用于指定要扫描的包。功能和 basePackages 属性功能一样。 - ③
@Bean
注解:该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
该注解的属性:
name:给当前 @Bean 注解方法创建的对象指定一个名称 (即 bean 的 id)。 - ④
@PropertySource
注解:用于加载 .properties 文件中的配置。 例如我们配置数据源时, 可以把连接数据库的信息写到properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
该注解的属性:
value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:
@PropertySource(value={"classpath:jdbc.properties"})
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name="dataSource")
public DataSource createDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
}
}
- ⑤
@Import
注解:此时我们已经有了两个配置类(SpringConfiguration、JdbcConfig),但是他们还没有关系。下面我们可以使用 @Import 注解,为这两个配置类建立关系链接。
用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问题。
该注解的属性:
value[]:用于指定其他配置类的字节码。
@Configuration
@ComponentScan(basePackages="pers.stringbug")
@PropertySource(value={"classpath:jdbc.properties"})
@Import(value={JdbcConfig.class})
public class SpringConfiguration {
/**
* 创建 QueryRunner 对象,并存入 Spring 容器中
* 其中 @Qualifier(value="dataSource") 指定 bean 参数 id
*/
@Bean(name="runner")
public QueryRunner createQueryRunner(@Qualifier(value="dataSource") DataSource dataSource) {
return new QueryRunner(dataSource);
}
}
3.1.2、依赖注入的类型
1)注入bean类型
示例如上
2)注入基本数据类型
示例如上
3)给List结构注入:List、Set集合,Array数组
List集合需要使用<list>标签来配置注入
Set集合需要使用<set>标签来配置注入,其配置参数及含义和<list>标签完全一样。
Array数组需要使用<array>标签来配置注入,其配置参数及含义和<list>标签完全一样。
- 实体类
public class School {
private List<String> studentList;
private Set<String> studentSet;
private String[] studentArray;
//省略setter、getter方法
}
- XML文件
<!-- 测试setter注入复杂类型 -->
<bean id="school" class="org.example.entity.School">
<property name="studentList">
<list>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</list>
</property>
<property name="studentSet">
<set>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</set>
</property>
<property name="studentArray">
<array>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</array>
</property>
</beans>
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
public class UserServiceTest {
//测试基于setter注入复杂数据类型
@Test
public void test(){
//读取配置文件
ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
//获取bean的实例
School school =(School) ctx.getBean("school");
//List结构注入
System.out.println(school.getStudentList());
System.out.println(school.getStudentSet());
System.out.println(Arrays.toString(school.getStudentArray()));
//Map结构注入
// System.out.println(school.getStudentAgeMap());
// System.out.println(school.getStudentClassProperties());
}
}
-
运行结果
4)给Map结构注入:Map集合,Properties对象
Map集合需要使用<map>标签来配置注入,其属性“key-type”和“value-type”分别指定“键”和“值”的数据类型。
Properties对象需要使用<props>标签来配置注入,键和值类型必须是String,不能变,子标签<prop key=”键”>值</prop>来指定键值对。
- 实体类
public class School {
private Map<String,Integer> studentAgeMap;
private Properties studentClassProperties;
//省略setter、getter方法
}
- XML文件
<!-- 测试setter注入复杂类型 -->
<bean id="school" class="org.example.entity.School">
<property name="studentAgeMap">
<map>
<entry key="张三" value="18"></entry>
<entry key="王五">
<value>16</value>
</entry>
</map>
</property>
<property name="studentClassProperties">
<props>
<prop key="张三">初一三班</prop>
<prop key="李四">初二五班</prop>
</props>
</property>
</bean>
</beans>
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
public class UserServiceTest {
//测试基于setter注入复杂数据类型
@Test
public void test(){
//读取配置文件
ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
//获取bean的实例
School school =(School) ctx.getBean("school");
//Map结构注入
System.out.println(school.getStudentAgeMap());
System.out.println(school.getStudentClassProperties());
}
}
-
运行结果
参考知乎作者「god23bin」的原创文章,原文链接:https://zhuanlan.zhihu.com/p/640517982
参考CSDN博主「ADAMs.」的原创文章,原文链接:https://blog.csdn.net/qq_43097201/article/details/104486887
参考博客园作者「LeeHua」的原创文章,原文链接:https://www.cnblogs.com/liyihua/p/14482488.html
3.2、AOP
简言之,就是在【编码时】把业务逻辑处理代码(例如:用户信息管理的业务逻辑功能)和额外的功能代码(日志记录,权限控制,事务处理等)【分离】开来,降低程序间的耦合度,使得软件的结构更加清晰
在【运行时】,使用【动态代理技术】把业务逻辑处理和额外功能处理【结合】到一块,既能完成业务逻辑处理,也能完成额外的功能。并且额外功能逻辑可以多次重复使用,减少了重复代码的开发,提高了程序的复用性
实现方式:
网友评论