文章目录
1. Spring概述
Spring 框架为基于Java的现代企业应用提供了全面的编程和配置模型, 是一个分层的JavaSE/JavaEE一站式的
轻量级开源框架.
Spring兴起于2003年, 前身来源于EJB架构
1.1 Spring的优势
方便解耦, 简化开发
支持AOP编程
支持声明式事务和编码式事务
声明式事务 : 提前在Spring主配置文件applicationContext中去配置事务
编码式事务 : 通过代码开启事务
方便`程序的测试 : Spring集成JUnit
集成各种框架 : JFnal, Mybatis,Struts2,Hibernate
框架的源码是需要我们学习的,有许多经典的设计思想,设计模式,优秀的框架等等
1.2 Spring Framework的体系架构
2. Spring环境搭建以及HelloWorld案例
引入依赖
<!--spring依赖-->
<dependency>
<!--只需要写一个就行了,因为会依赖传递,会自动加载相应的核心依赖-->
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
配置文件applicationContext.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">
<!--把User类交给Spring管理 控制反转
id : 对象的标识
class : 包名+类名
-->
<bean id="user" class="com.zy.pojo.User"/>
<!--<bean class="com.zy.service.impl.UserServiceImpl" id="userService"/>-->
<bean class="com.zy.mapper.impl.UserMapperImpl" id="userMapper"/>
</beans>
service层
public class UserServiceImpl implements UserService {
//private final UserMapper userMapper = new UserMapperImpl();
private static final UserService userService = new UserServiceImpl();
@Override
public List<User> getAllUser() {
//加载配置文件 : 借助于ApplicationContext接口的实现类ClassPathXmlApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//通过反射得到对象,第一个参数是配置文件的bean标签的id属性
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> userList = userMapper.getAllUser();
for (User user : userList) {
System.out.println(user);
}
return null;
}
public static void main(String[] args) {
userService.getAllUser();
}
3. schema
<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">
配置约束的目的是为了xml进行配置的时候能够按照提前约定好的约束进行标签的校验
1. xmlns="http://www.springframework.org/schema/beans"
xml ns --->namespace命名空间
一般配置一个xml最多只能有一个匿名的命名空间
对于Spring框架来说,它把匿名空间给了beans使用. 如果需要配置的话,那么当使用bean中的标签的时候,
需要在<aa:bean>
2. 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的约束,而整体xml采用的是schema约束, 所以我们需要配置schema约束地址,
schemaLocation约束采用key-value键值对的方式.
schemaLocation约束依赖于http://www.w3.org/2001/XMLSchema-instance
所以xml配置首先http://www.w3.org/2001/XMLSchema-instance给它起了一个别名xsi
4. IOC(控制反转)
IOC : Inversion of Control
将创建对象的控制权交给Spring管理,通过反射的方式创建对象,提前在Spring容器把对象信息构建好,我们可以
通过Spring容器调用getBean()获取对象.
BeanFactory通过xml的bean标签的class属性找到该bean,然后对该bean进行实例化
- ApplicationContext, 使用它需要借助它的实现类ClassPathXmlApplicationContext,默认会创建的时候,将内部配置的所有对象加载到Spring容器
- ClassPathXmlApplicationContext, 从classpath的目录下加载Spring主配置文件
Spring的本质就是对象管理容器, 该容器其实是Map
4.1 bean元素
属性
name属性 : 给创建的对象起一个名字,默认是类名,首字母小写
class属性 : 需要创建对象的类路径 反射机制
id属性 : id属性和name属性的作用是一样的,id名称不能重复,name可以重复,但是不建议
scope属性 : 创建bean元素对象的方式
singleton单例模式(默认) : 当前的对象在容器中只会创建一次, 容器只会保存该类的一个对象,
一个bean有且有一个对象.加载配置文件时就会创建对象
假设有多个class值一样的bean,那么也会生成多个不同的对象,但是还是单例模式
prototype多例模式 : 当前对象在容器中每次被调用getBean返回的都是新对象.
加载配置文件时不会创建对象,用到了才会创建对象
在Struct2容器中,采用的是多例模式.每个action都
request 请求域
session 会话 ,在服务器中
globalSession 跨服务
4.1.1 bean元素的三种创建方式
创建工厂类
public class BeanFactory {
//静态方式
public static User getBean() {
System.out.println("采用静态工厂方式创建对象");
return new User();
}
//成员方法 实例创建
public User getUSer() {
System.out.println("采用实例工厂方式创建对象");
return new User();
}
}
- 空参构造方式
<!--方式一 : 空参构造创建 user-->
<bean id="user" class="com.zy.pojo.User"/>
- 静态工厂方式(了解)
<!--方式二 : 静态工厂方式-->
<bean id="beanFactory" class="com.zy.pojo.BeanFactory" factory-method="getBean"/>
- 实例工厂方式(了解)
<!--方式三 : 实例工厂方式创建-->
<bean name="userBeanFactory" class="com.zy.pojo.BeanFactory"/>
<bean name="userFactory" factory-bean="userBeanFactory" factory-method="getUSer"/>
测试类
private ApplicationContext context = null;
@Before
public void before() {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
/**
* @author yxk
* Date 2021/3/3 15:10
* 空参构造bean元素
*/
@Test
public void testCreateBean() {
User user = context.getBean("user", User.class);
System.out.println(user.hashCode());
}
/**
* @author yxk
* Date 2021/3/3 15:22
* 静态工厂方式创建bean元素
*/
@Test
public void testStaticBean() {
User User = context.getBean("beanFactory", User.class);
System.out.println(User.hashCode());
}
/**
* @author yxk
* Date 2021/3/3 15:22
* 实例工厂方式创建bean元素
*/
@Test
public void testUserBean() {
User User = context.getBean("userFactory", User.class);
System.out.println(User.hashCode());
}
4.1.2 bean元素的生命周期
- init-method : 初始化方法 初始化对象(对象创建之后才开始化对象)
- destroy-method : 当对象销毁时,会执行该方法,但不是这个方法销毁的
第一种情况,这个对象长期不用,会被GC清除掉
第二种情况,当这个容器销毁/关闭的时候,存储在该容器的bean对象也会销毁 - bean元素默认是交给Spring管理的.但是如果把该bean元素的scope设置为prototype模式,Spring只会创建,但是不会销毁.在单例模式下,Spring管理bean元素的整个生命周期
5. DI(依赖注入)
有IOC环境支持,Spring创建这个类的过程中,Spring将类的依赖的属性设置进去
5.1 注入方式
如果是简单类型(基本类型,String),则使用**value**;如果是复杂类型(除了简单类型的其他类型)就用**ref**
- set方法注入
对象必须有set方法,否则无法正常注入
<!--依赖注入-->
<!--方式一 : set方式-->
<bean id="user" class="com.zy.pojo.User" >
<property name="id" value="1"/>
<property name="username" value="小明"/>
<property name="address" value="郑州"/>
<property name="gender" value="true"/>
<property name="birthday" ref="date"/>
<property name="role" ref="role"/>
</bean>
<!--配置对象-->
<bean id="role" class="com.zy.pojo.Role">
<property name="id" value="1"/>
<property name="desc" value="哈哈"/>
<property name="name" value="教师"/>
<property name="updateTime" ref="date"/>
</bean>
<bean id="date" class="java.util.Date"/>
- 构造方法注入
<!--方式二 : 构造方法注入-->
<bean id="user02" class="com.zy.pojo.User">
<!--
constructor-arg
name : 参数的名称
value : 参数的值
ref : 引用
index : 构造方法参数的索引
type : 参数的类型
name和index可以任选一个,或两个都写
-->
<constructor-arg index="0" value="1" type="java.lang.Integer"/>
<constructor-arg index="1" value="小明" type="java.lang.String"/>
<constructor-arg index="2" value="北京" type="java.lang.String"/>
<constructor-arg index="3" ref="date" type="java.util.Date"/>
<constructor-arg index="4" value="true" type="boolean"/>
</bean>
- p名称空间注入(了解)
本质还是借助set方法
<!--方式三 : p名称空间注入-->
<bean id="user03" class="com.zy.pojo.User" p:id="3" p:username="小明"
p:birthday-ref="date" p:gender="true" p:role-ref="role"/>
- SPEL表达式注入(了解)
<!--方式四 : SPEL表达式注入-->
<bean id="user04" class="com.zy.pojo.User">
<property name="id" value="#{role.id == 1 ? 1 : 2}"/>
<property name="username" value="#{user.username.equals('小明') ? '小黑' : '小红'}"/>
</bean>
- 复杂类型注入(数组,集合List Set Map)
实体类
自行提供setget等方法
private Object[] arr;
private List<Object> list;
private Set<Object> set;
private Map<Object,Object> map;
private Properties properties;
配置文件
<bean id="user01" class="com.zy.pojo.User">
<property name="username" value="小明"/>
<property name="address" value="北京"/>
</bean>
<bean id="user02" class="com.zy.pojo.User">
<property name="username" value="小红"/>
<property name="address" value="郑州"/>
</bean>
<!--复杂类型注入-->
<bean id="complexObject" class="com.zy.pojo.ComplexObject">
<!--数组类型注入-->
<property name="arr">
<array>
<!--是一个Object[]数组,即可以注入基本类型(用value),也可以是引用类型(用ref)-->
<value>Java</value>
<value>JavaScript</value>
<!--通过bean属性引入-->
<ref bean="user01"/>
</array>
</property>
<!--List类型注入-->
<property name="list">
<list>
<!--简单类型-->
<value>Python</value>
<value>C</value>
<!--引用类型-->
<ref bean="user02"/>
</list>
</property>
<!--Set类型注入-->
<property name="set">
<set>
<value>C++</value>
<value>C#</value>
<ref bean="user01"/>
</set>
</property>
<!--Map类型注入-->
<property name="map" >
<map>
<entry key="1" value="1"/>
<entry key="2" value="2"/>
<!--值为引用类型-->
<entry key="3" value-ref="user02"/>
</map>
</property>
<!--Properties类型注入-->
<property name="properties">
<props>
<prop key="课程">Java</prop>
<prop key="专业">计算机</prop>
<prop key="学院">信息工程学院</prop>
</props>
</property>
</bean>
测试
@Test
public void testDIComplex() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ComplexObject bean = context.getBean("complexObject", ComplexObject.class);
System.out.println(bean);
}
结果
注意 :
以上使用的复杂类型,只要它的存储结构相同,里面的嵌套标签可以混搭.
如 : Array,List,Set是单列结构,它们的子标签可以混用
Map,Properties是双列结构,它们的子标签也可以混用
6. Spring分模块配置文件开发
有时候我们为了方便阅读,会将配置文件按照不同的功能,模块分成多个xml文件,在引入的时候可以使用<import>
标签,集成到当前的配置文件,目的是当程序读取配置文件时就会把引入的其他配置文件全部读取进内存中
不同的Spring配置文件命名
dao层 : applicationContext-dao.xml
service层 : applicationContext-service.xml
web层 : applicationContext-mvc.xml
**注意事项** : 引入的时候,它是一个类路径(classes),也是一个相对路径
<!--在主配置文件中引入,只需要加载主配置文件就行了-->
<import resource="applicationContext-dao.xml"/>
<import resource="applicationContext-service.xml"/>
<import resource="applicationContext-mvc.xml"/>
6.1 注解开发
6.1.1组件扫描
@Component pojo
@Controller web层
@Service service层
@Repository dao层
在类的头上使用注解时,默认生成的对象名称就是类名,但是首字母小写
需要在配置文件中开启注解扫描,开启了组件扫描注解才能生效
<!--开启组件扫描-->
<context:component-scan base-package="com.zy"/>
6.1.2 bean元素的创建
- @Scope : 标记在类上面
@Scope(scopeName = "singleton")//单例模式下创建对象
6.1.3 生命周期
- @postConstruct : 标记在初始化方法上面,构造方法执行之后调用
- @preDestroy : 标记在销毁方法上面
//初始化方法
@PostConstruct
public void init() {
System.out.println("构造方法执行后,初始化方法执行");
}
//销毁方法
@PreDestroy
public void destroy() {
System.out.println("容器关闭前,销毁该方法");
}
测试
@Test
public void testLifeCycle() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user", User.class);
//手动关闭容器
((ClassPathXmlApplicationContext)context).close();
}
备注 : 销毁方法的执行需要在单例模式下才会执行
6.1.4 属性注入
- 简单类型(基本类型+String字符串)
@Value 注入属性值 , 标记在属性上面或set方法上
@Value("小赵")
private String username;
@Value("北京")
private String address;
- 引用类型
@Autowired
根据容器中的类型自动匹配,进行注入.一般情况下如果Spring容器有多个该类型,
那就会报期望的值和从容器查找到的值数量不匹配
注意 : pojo通过spring的依赖注入把自己交给spring容器管理,我们自己new的对象是拿不到依赖注入的属性的值,因为根本不是同一个对象
@Quafilfier
指定该属性引用容器中哪个bean对象, 和@Autowired 搭配使用
在项目中,如果该类在容器只配置一次,那么可以放心使用@Autowired
@Resource
可以理解为 @Resource = @Autowired + @Quafilfier
@Resource有一个name属性,它是指定使用哪个bean,如果Spring容器有多个该类型,就需要name指定,如果只有一个那就不需要
@Resource
private Car car;//引用类型 : 类对象
6.1.5 纯注解开发
- @Configuration
该注解是一个配置性的注解,指定当前类是一个Spring的配置类 - @ComponentScan(“组件扫描的包名”)
组件扫描注解,替代<context:component-scan base-package=“com.zy”/> - @Bean
该注解只能写在方法上面,表明调用此方法,该方法会返回一个对象,并把对象存放到Spring容器中,等价于xml的bean标签
主要用来配置非自定义的bean,比如DruidDataSource、SqlSessionFactory - @PropertySource
*主要加载properties文件
@Configuration
@PropertySource
public class DBUtil{
@Value("${jdbc.driverClass}")
private String driver
@Value("${jdbc.url}")
private String url
@Value("${jdbc.usernmae}")
private String username
@Value("${jdbc.password}")
private String password
@Bean
public Connection getConnection() {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource.getConnection();
}
}
- import
主要把其他配置类导入主配置类,等价于
<import resource="applicationContext-dao.xml"/>
主配置类
@Configuration//标记该类为配置类
@ComponentScan("com.zy")//开启组件扫描
@Import(DBUtil.class)//引入其他配置类
public class Annotation {
}
其他配置类
@Configuration
@PropertySource("classpath:db.properties")
public class DBUtil {
@Value("${jdbc.driverClass}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public Connection getConnection() {
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource.getConnection();
}catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
}
7. Spring整合Junit
减少测试中不停的构建Spring容器,如以下代码就可以省略掉
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserController userController = context.getBean("userController", UserController.class);
- 步骤
- 添加依赖
<!--Spring整合Junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.11.RELEASE</version>
<scope>test</scope>
</dependency>
- 在测试类添加注解@RunWith(),让它帮我们创建Spring容器
@RunWith(SpringJUnit4ClassRunner.class)
- 在测试类添加注解@ContextConfiguration(“主配置文件的路径或主配置类的字节码对象”)
@ContextConfiguration("classpath:applicationContext.xml")//加载主配置文件
//或
@ContextConfiguration(classes =MasterConfiguration.class)//加载主配置类
- 从容器中取出需要的bean元素
getBean()
或
@AutoWired,@Resource注解注入 - 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {
@Resource(name = "car2")
private Car car;
@Resource
private UserController userController;
/**
* @author yxk
* Date 2021/3/4 15:32
* 测试
*/
@Test
public void test01() {
System.out.println(car);
userController.test();
}
}
8. Spring整合JDBC
Spring框架提供了一个操作数据库的对象, 这个对象封装了JDBC实现的细节, 提供了一个模板,这个模板类是JdbcTemplate,该类在spring-jdbc.jar包中
步骤
导包 : spring核心包+spring-jdbc+数据库驱动包+连接池jar+spring-text+junit
准备数据库表user
dao层
service层
controller层
junit单元测试
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
</dependencies>
db.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///数据库名称?useSSL=true&useUnicode=true&characterEncoding=utf-8
jdbc.username=数据库用户名
jdbc.password=数据库密码
spring配置文件引入db.properties
<context:property-placeholder location="classpath:db.properties"/>
配置文件
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<!--采用c3p0数据库连接池, 它依赖于数据源dataSource ComboPooledDataSource
把ComboPooledDataSource对象注入到Spring容器中
-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置jdbcTemplate对象 , 也需要依赖于数据源dataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--属性注入,采用set方式-->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
dao层
@Repository
public class UserDaoImpl implements UserDao {
@Resource
private JdbcTemplate jdbcTemplate;
//添加
@Override
public void addUser(User user) {
int rowNum = jdbcTemplate.update("insert into user values (null,?,?,?)", user.getU_name(), user.getU_age(), user.getU_salary());
if (rowNum > 0) {
System.out.println("数据添加成功");
}else {
System.out.println("数据添加失败");
}
}
//更新
@Override
public void updateUserById(User user) {
int rowNum = jdbcTemplate.update("update user set u_name = ?,u_age = ?,u_salary = ? where u_id = ?", user.getU_name(), user.getU_age(), user.getU_salary(), user.getU_id());
if (rowNum > 0) {
System.out.println("更新成功");
}else {
System.out.println("跟新失败");
}
}
//根据ID删除用户
@Override
public void deleteUserById(int u_id) {
jdbcTemplate.update("delete from user where u_id = ?",u_id);
}
//查询所有
@Override
public List<User> getAllUser() {
return jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));
}
//根据用户id查询
@Override
public User getOnrUser(int u_id) {
return jdbcTemplate.queryForObject("select * from user where u_id = ?", new BeanPropertyRowMapper<>(User.class), u_id);
}
//分页+模糊
@Override
public List<User> selectFuzzyByUsername(String username, int beginIndex) {
List<User> userList = null;
try{
userList = jdbcTemplate.query("select * from user where u_name like ? limit ?,3", new BeanPropertyRowMapper<>(User.class), username, beginIndex);
return userList;
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
//查询总记录数
@Override
public int selectUserCount() {
return jdbcTemplate.queryForObject("select count(1) from user", Integer.class);
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateTest {
@Resource
private UserDao userDao;
@Resource
private User user;
/**
* @author yxk
* Date 2021/3/5 10:14
* 测试add
*/
@Test
public void testAddUser() {
userDao.addUser(user);
}
/**
* @author yxk
* Date 2021/3/5 10:36
* 跟新
*/
@Test
public void testUpdateUserById() {
User user = new User();
user.setU_age(18);
user.setU_name("安琪拉");
user.setU_id(11);
userDao.updateUserById(user);
}
/**
* @author yxk
* Date 2021/3/5 10:41
* 删除
*/
@Test
public void testDeleteUserById() {
userDao.deleteUserById(11);
}
/**
* @author yxk
* Date 2021/3/5 10:43
* 查询所有用户
*/
@Test
public void testSelectAllUser() {
List<User> userList = userDao.getAllUser();
for (User user : userList) {
System.out.println(user);
}
}
/**
* @author yxk
* Date 2021/3/5 10:44
* 查询一个用户
*/
@Test
public void testSelectUserById() {
User user = userDao.getOnrUser(10);
System.out.println(user);
}
/**
* @author yxk
* Date 2021/3/5 10:46
* 模糊+分页
*/
@Test
public void testSelectFuzzyByUsername() {
List<User> userList = userDao.selectFuzzyByUsername("%小%", 1);
for (User user : userList) {
System.out.println(user);
}
}
/**
* @author yxk
* Date 2021/3/5 10:47
* 查询总记录数
*/
@Test
public void testSelectUserCount() {
int count = userDao.selectUserCount();
System.out.println(count);
}
}
注意pojo需要交给spring管理,使用注解 @Component
9 使用纯注解操作CRUD
- @Configuration
该注解是一个配置性的注解,指定当前类是一个Spring的配置类 - @ComponentScan(“组件扫描的包名”)
组件扫描注解,替代<context:component-scan base-package=“com.zy”/> - @Bean
该注解只能写在方法上面,表明调用此方法,该方法会返回一个对象,并把对象存放到Spring容器中,等价于xml的bean标签
主要用来配置非自定义的bean,比如DruidDataSource、SqlSessionFactory - @PropertySource
主要加载properties文件 ,等价于
<context:property-placeholder location="classpath:db.properties"/>
- @Import
主要把其他配置类导入主配置类,等价于
<import resource="applicationContext-dao.xml">
- @ContextConfiguration(classes = 主配置类的字节码对象)
加载配置类,等价于
主配置类
@Configuration//把该类设置为配置类,替代spring的xml文件
@ComponentScan("com.zy")//开启组件扫描,扫描com.zy下的所有包的类
@Import(DBUtilsConfiguration.class)//引入其他副配置类
public class SpringConfiguration {
}
副配置类
/**
* @author yxk
* @version 1.0
* Date 2021/3/5 11:13
* 数据库配置类,用来连接数据库,是一个副配置类,需要引入到主配置类
*/
@Configuration
@PropertySource("classpath:db.properties")
public class DBUtilsConfiguration {
@Value("${jdbc.driverClass}")
private String driverClass;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* @author yxk
* @return javax.sql.DataSource
* Date 2021/3/5 11:25
* 配置数据源,使用c3p0连接池
*/
@Bean
public DataSource getDataSource() {
try {
//构建ComboPooledDataSource,注入数据源信息
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driverClass);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
} catch (PropertyVetoException e) {
e.printStackTrace();
}
return null;
}
/**
* @author yxk
* @param dataSource
* @return org.springframework.jdbc.core.JdbcTemplate
* Date 2021/3/5 11:32
* 把数据源和JdbcTemplate关联起来,使得JdbcTemplate能访问数据库
* 使用@Autowired注解使用spring容器的dataSource
*/
@Bean
public JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
测试类
测试类所用到的注解
@RunWith(SpringJUnit4ClassRunner.class)//spring整合junit
@ContextConfiguration(classes = SpringConfiguration.class)//加载主配置类
public class JdbcTemplateTest {}
10 spring-web
此时和页面交互的还是使用servlet
导入servlet的依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.2.1</version>
</dependency>
web.xml
需要把Spring容器存放到servletContext域对象中.一个web应用只有一个servletContext域
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 当web应用启动立马加载Spring的主配置文件-->
<!-- 通过key为contextConfigLocation的全局初始化参数找到对应的值,就是Spring的主配置文件
通过读取applicationContext.xml文件,创建Spring容器
该Spring容器被存放到servletContext域对象中.一个web应用只有一个servletContext域
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 监听器 监听servletContext全局域对象-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
servlet(部分)
- 因为该类是由tomcat提供的,不能在此类中使用spring的属性注入注解,虽然没有报错,但是生成的userDao是没有初始化的,所以,我们需要在web.xml配置项目启动时就加载配置文件,把生成的spring容器存放到servletContext域中.然后在servlet类的初始化方法init()中通过WebApplicationContextUtils得到spring容器中的bean对象
//@Autowired
private UserDao userDao;
//spring中提供了一个类WebApplicationContextUtil 读取ServletContext全局域对象中的信息
@Override
public void init() throws ServletException {
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
assert context != null;
userDao = context.getBean("userDao", UserDao.class);
}
11动态代理
Spring有两种代理模式 :
JDK动态代理 : JDK原生
CGLIB动态代理 : 第三方插件
11.1 JDK动态代理
主要是对应用中的业务层进行增强
Proxy类 是JDK的一个代理类
使用该类中的一个方法 newProxyInstance()
该方法的三个参数 :
ClassLoader : 类加载器 , 要求和目标对象是一样的类加载器,它是固定写法
Class<?>interface : 实现的接口,要求目标对象实现的所有接口,它是固定写法
InvocationHandler : 代理的方式,如何对目标对象进行增强,它是一个接口,所以需要用匿名内部类写它的实现类
在该接口当中定义了一个抽象方法invoke()
invoke方法也有三个参数 :
Object proxy : 指的是需要被代理的目标对象
Method method : 当前要执行的目标对象中的方法
Object[] args : 当前执行方法中用到的参数
代理类
@Component
public class JDKProxy {
public UserService getProxyUserService(UserService userService) {
UserService us =(UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK开始动态代理selectUserByUsername方法");
Object invoke = method.invoke(userService, args);//反射机制
System.out.println("JDK开始动态代理");
return invoke;
}
});
return us;
}
/* public static void main(String[] args) {
JDKProxy jdkProxy = new JDKProxy();
//是自己new的没有交给Spring管理,需要把UserServiceImpl的dao注入注释
UserService proxyUserService = jdkProxy.getProxyUserService(new UserServiceImpl());
proxyUserService.selectUserByUsername("小孩");
}*/
}
测试
//注入UserService类对象
@Resource
private UserService userService;
@Resource
private JDKProxy jdkProxy;
/**
* @author yxk
* Date 2021/3/4 16:57
* 测试JDK动态代理
*/
@Test
public void testJDKProxy() {
UserService proxyUserService = jdkProxy.getProxyUserService(userService);
User user = proxyUserService.selectUserByUsername("小孙");
System.out.println(user);
}
结果
11.2 CGLIB动态代理
原理 : 不需要借助于接口,一般情况下在继承关系 ==>父子类
要求 ; 目标类不能是最终类
借助Enhancer类的create()方法,该方法的参数
class : 目标对象的字节码对象
callback : 需要借助一个MethodInterceptor接口 方法拦截器
代理的方式 : 通过拦截器的方式实现对目标对象中的方法进行增强
该接口需要使用匿名内部类 并且有一个抽象方法intercept(),该方法的参数有
Object proxy : 代理的目标方法
Method method : 当前要执行的目标对象的方法
Object[] objects : 当前执行方法中用到的参数
- 引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
/**
* @author yxk
* @version 1.0
* Date 2021/3/4 17:15
* CGLIB动态代理
* 原理 : 不需要借助于接口,一般情况下在继承关系 ==>父子类
* 要求 ; 目标类不能是最终类
* 借助Enhancer类的create()方法,该方法的参数
* class : 目标对象的字节码对象
* callback : 需要借助一个MethodInterceptor接口 方法拦截器
* 代理的方式 : 通过拦截器的方式实现对目标对象`中的方法进行增强
* 该接口需要使用匿名内部类 并且有一个抽象方法intercept(),该方法的参数有
* Object proxy : 代理的目标方法
* Method method : 当前要执行的目标对象的方法
* Object[] objects : 当前执行方法中用到的参数
*
*
*
*/
@Component
public class CGLIBProxy {
public UserService getProxyUserService(UserService userService) {
UserService o = (UserService)Enhancer.create(userService.getClass(), new MethodInterceptor() {
//intercept拦截器
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开启事务");
Object invoke = method.invoke(userService, objects);
System.out.println("提交事务");
return invoke;
}
});
return o;
}
}
测试
@Resource
private UserService userService;
@Resource
private CGLIBProxy cglibProxy;
@Test
public void testCglibProxy() {
UserService proxyUserService = cglibProxy.getProxyUserService(userService);
User user = proxyUserService.selectUserByUsername("哈哈");
System.out.println(user);
}
12. AOP(面向切面编程)
切面 : 切入点(方法) + 通知(增强的内容)
AOP Aspect Oriented Programming, 也是一种编程思想,主要的工作就是对应用当中重复的代码进行横向抽取
在程序运行当中采用动态代理技术把一些代码内容植入应用当中,对目标方法进行增强
基于动态代理实现的,底层依赖于反射机制, 对应用当中出现的公共代码进行横向抽取.放到切面中(通知)
专业术语
- 连接点 JoinPoint
目标对象中可以增强的方法称之为连接点
可增强,也可不增强 - 目标对象 Target
需要被增强的类对象 - 切入点 Pointcut
目标对象中要增强的方法称之为切入点
必须增强 - 通知/增强 Advice
在目标对象中要增强的方法中添加增强的内容称之为通知 - 织入 Weaving
将通知应用到连接点的过程称之为织入 - 代理 Proxy
生成的代理对象
如由spring创建的UserService类对象通过动态代理技术生成的代理对象称之为代理 - 切面
切面 = 切入点+通知
12.1 AOP快速入门
开发步骤
- 准备service层
- 把类交给spring管理
- 除了spring的常规jar之外,还需要引入一下依赖
aopalliance
aspectJweaver
spring-aspects - 配置通知
前置通知before()
后置通知after-returning(), 切入点有异常,则会中断
异常通知after-throwing(), 切入点有异常信息,则会织入该方法的增强内容
环绕通知around(), 可以看成前置通知+后置通知+异常通知, 所以有了环绕通知就不需要配置这三个通知了
最终通知after(), 不管切入点是否有异常都会织入该方法定义的增强内容 - 切入点没有异常时通知的执行顺序:
前置通知—>环绕通知的前半部分–>切入点方法–>最终通知–>环绕通知后半部分–>最终通知
public class MyAdvice {
//通知内容需要放在方法里
//前置通知
public void before() {
System.out.println("前置通知........已经连接上数据库");
}
}
- 配置切入点,在配置文件里配置切入点
- 通过切入点和通知形成切面,借助于aop:aspect
- 切入点的表达式
execution(public void com.zy.service.impl.UserServiceImpl.addUser(…))
定位哪个包下的哪个类的哪个方法是切入点,但这就写死了只能指定一个方法为切入点
- void 改成 * 表示适配方法的任意返回值
- 方法里的两个点 … 表示通配所以参数
- 把addUser(…)改成 *(…) 表示UserServiceImpl类下的所有方法为切入点
- 把UserServiceImpl.addUser(…)该成 * . * (…)表示impl包下的所有类的所有方法为切入点
- service.impl.UserServiceImpl.addUser(…)改成service. . * . * (…) 表示service包下及它的子包下的所有类的所有方法为切入点
- public可以省略不写
<!--配置aop-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pct" expression="execution(public void com.zy.service.impl.UserServiceImpl.addUser(..))"/>
<!--配置切面 -->
<aop:aspect ref="myAdvice" >
<!--把通知的方法关联切入点-->
<aop:before method="before" pointcut-ref="pct"/>
</aop:aspect>
</aop:config>
纯基于xml
通知类
//通知内容需要放在方法里
//前置通知
public void before() {
System.out.println("前置通知...已经连接上数据库");
}
//后置通知
public void afterReturning() {
System.out.println("后置通知...");
}
//异常通知
public void afterThrowing() {
System.out.println("异常拦截通知...切入点有异常");
}
//环绕通知
public void around(ProceedingJoinPoint joinPoint) {
try {
System.out.println("环绕通知...方法执行前加载信息");
joinPoint.proceed();//类似于反射的Method 的invoke()方法
System.out.println("环绕通知...方法执行后释放信息");
} catch (Throwable throwable) {
System.out.println("环绕通知...异常发生时处理...");
throwable.printStackTrace();
}
}
//最终通知
public void after() {
System.out.println("最终通知...切入点一定会执行");
}
配置文件
<?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 class="com.zy.advice.MyAdvice" id="myAdvice"/>
<!--配置aop-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pct" expression="execution(* com.zy.service..*.*(..))"/>
<!--配置切面 -->
<aop:aspect ref="myAdvice" >
<!--把通知的方法关联切入点-->
<aop:before method="before" pointcut-ref="pct"/>
<aop:after-returning method="afterReturning" pointcut-ref="pct"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pct"/>
<aop:around method="around" pointcut-ref="pct"/>
<aop:after method="after" pointcut-ref="pct"/>
</aop:aspect>
</aop:config>
</beans>
12.2 基于注解的AOP配置
开发步骤
- 让目标对象和通知类交给spring管理,并且使用@Aspect注解使通知类有切面的条件
- 配置切面
- 配置切入点. 在通知类创建一个空方法,然后使用@Pointcut注解,写上表达式
- 把切入点和通知进行绑定
- 开启Spring支持AOP注解
纯注解开发:在通知类或配置类使用该注解:@EnableAspectJAutoProxy开启Spring支持AOP注解
xml开发 : 在主配置文件中开启 : <aop:aspectj-autoproxy/>
代码
该代码为纯注解开发,如果你是xml开发,首先基于前面aop的配置,然后在配置文件上加上 <aop:aspectj-autoproxy /> ,并且去掉@EnableAspectJAutoProxy注解
配置类
@Configuration
@ComponentScan("com.zy")
@EnableAspectJAutoProxy
public class SpringAnnotation {
}
通知类
@Component("annotation_Advice")
@Aspect//注册切面
public class Annotation_Advice {
@Pointcut("execution(* com.zy.service..*.*(..))")//定位切入点
public void pointcut(){}
//前置通知
@Before("pointcut()")//把通知和切入点绑定形成切面
public void before() {
System.out.println("前置通知...已经连接上数据库");
}
//后置通知
@AfterReturning("pointcut()")
public void afterReturning() {
System.out.println("后置通知...");
}
//异常通知
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("异常拦截通知...切入点有异常");
}
//环绕通知
@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) {
try {
System.out.println("环绕通知...方法执行前加载信息");
joinPoint.proceed();//类似于反射的Method 的invoke()方法
System.out.println("环绕通知...方法执行后释放信息");
} catch (Throwable throwable) {
System.out.println("环绕通知...异常发生时处理...");
throwable.printStackTrace();
}
}
//最终通知
@After("pointcut()")
public void after() {
System.out.println("最终通知...切入点一定会执行");
}
}
13 Spring-tx声明式事务
不管哪种框架都要实现Spring中提供的PantformTransactionManager接口. 让你的数据库操作交给Spring事务管理.Spring中的事务都是基于AOP的
Spring事务的API介绍
- PantformTransactionManager接口
获取事务状态信息 TransactionStatus getTrabsactionsaction(TransactionDifinition difinition)
提交事务 void commit(TransactionStatus status)
回滚事务 void rollback(TransactionDifinition difinition) - TransactionDifinition
事务定义的信息对象
获取事务对象的名称 String getName()
获取事务的隔离级别 int getlsolationLevel()
获取事务的传播行为 int getPropagationBehavior()
获取事务超时时间 int getTimeout()
获取事务是否只读 boolean idReadOnly() - TransactionStatus
事务的传播行为
主要解决的问题是在业务方法进行调用的时候,事务应该如何进行处理
- 保证在同一事务当中
PROPAGATION_REQUIRED 支持当前事务,如果不存在就新建一个(默认)
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常 - 保证不在同一事务中
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行
超时时间
如果没有设定超时时间,默认是没有超时限制的. 如果设置了,单位是以秒为单位
是否设置只读事务
对于增删改设定为false,对于查询设置为true. 默认是false
JDBC事务控制的演示
//用来演示JDBC(编码式)事务控制(不推荐)
@Resource
private DataSource dataSource;
@Override
public void addUserOne(User user) {
Connection connection = null;
try {
connection = dataSource.getConnection();
//开启事务
connection.setAutoCommit(false);
PreparedStatement ps = connection.prepareStatement("insert into user values (null,?,?,?)");
ps.setString(1,user.getU_name());
ps.setInt(2,user.getU_age());
ps.setDouble(3,user.getU_salary());
int rowNum = ps.executeUpdate();
//事务提交
int i =1/0;
connection.commit();
if (rowNum > 0) {
System.out.println("数据添加成功");
}else {
System.out.println("数据添加失败");
}
} catch (Exception e) {
//有异常,进行回滚
try {
assert connection != null;
connection.rollback();
} catch (SQLException throwable) {
System.out.println("事务回滚异常"+throwable.getMessage());
}
System.out.println("connection异常"+e.getMessage());
}
}
13.1 基于xml配置的声明式事务
当发生异常时,我们用try catch把异常捕获了.spring就认为没有发生异常,所以事务就不会发生回滚,
所以我们需要在catch里面进行手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
- 配置文件applicationContext.xml
<!--开启组件扫描-->
<context:component-scan base-package="com.zy"/>
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<!--采用c3p0数据库连接池, 它依赖于数据源dataSource ComboPooledDataSource
把ComboPooledDataSource对象注入到Spring容器中
-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置jdbcTemplate对象 , 也需要依赖于数据源dataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--属性注入,采用set方式-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--引用applicationContext-dao.xml的数据源dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知,关联事务管理器-->
<ts:advice id="txAdvice" transaction-manager="transactionManager"><!--默认去找id为transactionManager的事务管理器-->
<ts:attributes>
<!-- * 代表所有方法
timeout : 默认为-1,表示没有超时时间限制
read-only : 只读,默认为false
isolation : 隔离性,默认为可重复读
propagation:传播行为,默认是REQUIRED
-->
<ts:method name="*" read-only="false" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" />
<ts:method name="queryUserById" read-only="true"/>
<ts:method name="changSalary" read-only="false"/>
</ts:attributes>
</ts:advice>
<!--配置AOP切入点-->
<aop:config>
<aop:pointcut id="pct" expression="execution(* com.zy.service..*.*(..))"/>
<!--配置aop通知,可以关联到事务通知和切入点-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pct"/>
</aop:config>
dao层
需要注意的是当使用try catch把异常捕获了,spring就认为没有发生异常,所以事务就不会发生回滚,但是不捕获异常或者直接抛出异常,程序都会停止,这不太符合需求,所以我们需要捕获异常,然后在catch里面手动进行事务回滚
public boolean changSalary(String descUsername, String ascUsername, double money) {
//把降工资的人查询出来
User descUser = userDao.queryUserByUsername(descUsername);
//升工资的人
User ascUser = userDao.queryUserByUsername(ascUsername);
//调整工资
descUser.setU_salary(descUser.getU_salary() - money);
ascUser.setU_salary(ascUser.getU_salary() + money);
//更新两个人的工资
try {
userDao.updateUserByUsername(descUser);
int i = 1/0;
userDao.updateUserByUsername(ascUser);
}catch (Exception e) {
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return true;
}
13.2 基于注解的声明式事务
- 在基于xml的声明式事务下,先把xml里面的配置事务的东西注释掉(配置事务管理器以下),然后在xml加上 < aop:annotation-driven/>,用来开启事务注解支持
- 在service层的类上面或方法上使用注解@Transactional就表示该类或方法开启了事务
可以在使用该注解的属性设置传播行为,是否只读,是否回滚,隔离级别等
没有特殊要求,一般默认就行了
@Transactional(propagation = Propagation.REQUIRED,readOnly = true,isolation = Isolation.DEFAULT,rollbackFor = Exception.class)
13.3 基于纯注解的声明式事务
- 数据源类
配置数据源,创建DataSource对象,JdbcTemplate对象
@PropertySource("classpath:db.properties")
public class DBUtil {
@Value("${jdbc.driverClass}")
private String driverClass;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* @author yxk
* @return javax.sql.DataSource
* Date 2021/3/5 22:55
* 配置数据源
*/
@Bean
public DataSource getDataSource() {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
try {
dataSource.setDriverClass(driverClass);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
} catch (PropertyVetoException e) {
System.out.println("获取数据源失败:"+e.getMessage());
}
return dataSource;
}
/**
* @author yxk
* @param dataSource
* @return org.springframework.jdbc.core.JdbcTemplate
* Date 2021/3/5 22:55
* 使数据源与JdbcTemplate关联
*/
@Bean
public JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
配置事务类
public class TransactionConfig {
@Bean
public PlatformTransactionManager createTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
- 主配置类
@Configuration
@ComponentScan("com.zy")
@EnableTransactionManagement()//具有事务管理的能力
@Import({DBUtil.class,TransactionConfig.class})
public class SpringConfiguration {
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class JdbcTemplateTest {
@Resource
private UserService userService;
@Resource
private UserDao userDao;
@Resource
private User user;
@Test
public void testChangSalary() {
boolean flag = userService.changSalary("李四", "张三", 1000);
System.out.println(flag);
}
}