Spring(二)
1.DI注解
@Autowired注解细节
@Resource注解细节
2.IoC注解
Scope 和 PostConstruct 以及 PreDestroy 注解
3.使用DI注解与IoC注解简化模拟实例
Spring容器可以自己存自己,为什么这样设计?
4.代理模式
静态代理
动态代理
JDK动态代理
CGLIB 动态代理
DI注解
@Value:注入常量值,贴字段或者setter方法
@Autowired:注入bean,贴字段或者setter方法
@Resource:找bean的方式跟Autowired不一样
@Autowired注解细节
- 可以让Spring自动把属性或字段需要的对象找出,并注入到属性或字段上
- 可以贴在字段或者setter方法上面,贴在字段相当于提供了(setter)
- 可以同时注入多个对象
- 可以注入一些 Spring 内置的重要对象,比如 BeanFactory,ApplicationContext 等
- 默认情况下 Autowired 注解必须要能找到对应的对象,否则报错。通过 required=false 来避免这 个问题:@Autowired(required=false)
- Autowired 注解寻找 bean 的方式
1.首先按照依赖对象找,找到,使用setter或者字段注入
2.如果在Spring上下文(ApplicationContext) 中找到多个匹配的类型,再按照名字寻找,没有匹配报错
3. 使用@Autowire自动注入的时候,加上@Qualifier(“other”)可以指定注入哪个对象;
@Resource注解细节
基本同@Autowired,找bean的方式不一样
1.首先按照名字去找,如果找到,就使用 setter 或者字段注入。
2.若按照名字找不到,再按照类型去找,但如果找到多个匹配类型,报错。
3.可以直接使用 name 属性指定 bean 的名称(@Resource(name=""));但若指定的
name,就只能按照 name 去找,若找不到,就不会再按照类型去。
使用@Autowired注解相当于简化了xml匹配文件中的这一段话
<property name="" ref=""/>
注意:
@Setter仅仅用来生成set方法,没有注入的效果,不会给对象设置属性值或者字段值
要想Spring给对象设置属性值或者字段值,写两种配置
XML配置
<property name="属性名" value="属性值"|ref="容器另外一个bean名称"/>
注解配置
可以贴字段或者setter方法,若是贴字段上,是可以不用提供setter方法,@Value一定要提供setter方法
IoC注解
使用IoC注解可以简化xml配置文件中的bean配置
常用的有:
@Repository
:用于标注数据访问组件,即 DAO 实现类上。@Service
:用于标注业务层实现类上。@Controller
:用于标注控制层类上(如 SpringMVC 的 Controller)@Component
:当不是以上的话,可以使用这个注解进行标注。
Scope 和 PostConstruct 以及 PreDestroy 注解
@Scope
:贴在类上,标明 bean 的作用域。@PostConstruct
:贴在方法上,标明 bean 创建完后调用此方法。@PreDestroy
:贴在方法上,标明容器销毁时调用此方法。
使用DI注解与IoC注解简化模拟实例
- DAO层
public interface IUserDAO {
void save(String username,String password)throws Exception;}
- DAO实现类
@Repository //dao层实现类使用 @Repository
public class UserDAOImpl implements IUserDAO {
private DataSource dataSource;
//使用@Autowired注入属性
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void save(String username, String password) throws Exception {
@Cleanup //lombook的注解,关闭资源
Connection connection = dataSource.getConnection();
@Cleanup
PreparedStatement preparedStatement =
connection.prepareStatement("insert into user(username, password) VALUES (?,?)");
preparedStatement.setObject(1,username);
preparedStatement.setObject(2,password);
preparedStatement.executeUpdate();
}
}
- Service层
public interface IUserService {
void register(String username,String password)throws Exception;}
- service实现类
@Service //业务层实现类使用 @Service
public class UserServiceImpl implements IUserService {
private IUserDAO userDAO;
@Autowired
public void setUserDAO(IUserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public void register(String username, String password) throws Exception {
userDAO.save(username,password);}}
- applicationContext.xml配置文件
//头省略
<!--引入db.properties配置文件-->
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<!--配置好IoC DI注解解析器
让Spring 帮我们创建业务对象,DAO对象,并设置好属性值
-->
<context:component-scan base-package="cn.k"/>
</beans>
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {
@Autowired
private IUserService userService;
@Test
public void testUser() throws Exception {
userService.register("111","123");
}
}
注意:记得第三方配置
<context:component-scan base-package="贴了上面注解的类所在包路径"/>
思考
Spring容器可以自己存自己,为什么这样设计?
比如我从容器获取某种特征对象,比如贴@Controller注解所有的对象,怎么实现这个需求?
只要获取到容器对象,就可以拿到所有具有某种特征对象
@Autowrited
//容器或者应用上下文,容器获取到了容器,代表容器存到了自己
private ApplicationContext ctx;
//获取容器中所有贴Controller注解的对象
ctx.getBeansWithAnnotation(Controller.class);
代理模式
**为什么会有代理模式的理念?**例如在开发中,我们需要代码责任分离,如果一个业务层掺杂了事务的各种代码,会导致代码结构重复,责任不重复.
代理分类
-
静态代理:程序运行前已经存在代理类(代理类自己创建)字节码文件,代理对象和真实对象的关系前运行前就确定(代理类和对象都要自己创建)
-
动态代理:代理类是在程序运行期间由 JVM 通过反射等机制动态的生成的,所以不存在代理类的 字节码文件,动态生成字节码对象,代理对象和真实对象的关系是在程序运行时期才确定的。(即 代理类及对象不要我们自己创建)
静态代理
需求:给业务类方法添加模拟事务
- 业务层接口与实现类
public interface IUserService {
void save(String username,String password);
}
--------------------------------------------------------
//真实类 真实对象
public class UserServiceImpl implements IUserService {
//模拟
@Override
public void save(String username, String password) {
System.out.println("保存了"+username+""+password);
}
}
- 创建代理类
//代理类 代理对象
public class UserServiceProxy implements IUserService {
//与真实类保持联系
private IUserService target;
@Autowired
public void setTarget(IUserService target) {
this.target = target;}
private MyTransactionManager tx;
@Autowired
public void setTx(MyTransactionManager tx) {
this.tx = tx;}
@Override
public void save(String username, String password) {
try {
tx.begin();
//调用真实对象的方法
target.save(username,password);
tx.commit();
} catch (Exception e) {
tx.rollback();}}}
- 把事务代码抽取出来 tx
//封装事务相关的操作
public class MyTransactionManager {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
public void rollback(){
System.out.println("回滚事务");
}
}
- 配置applicationContext.xml
<bean id="tx" class="cn.k.tx.MyTransactionManager"/>
<bean id="proxy" class="cn.k.service.impl.UserServiceProxy">
<!--配置真实对象,藏起来-->
<property name="target">
<bean class="cn.k.service.impl.UserServiceImpl"/>
</property>
<property name="tx" ref="tx"/>
</bean>
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {
@Autowired
private IUserService Proxy;//这里注意使用xml配置对应实现类的名字,不然会找不到,因为这里使用的是多态接口
@Test
public void save() {
/* //创建房东
IUserService userService = new UserServiceImpl();
//创建中介
UserServiceProxy proxy = new UserServiceProxy();
//中介为上面房东做服务
proxy.setTarget(userService);
proxy.setTx(new MyTransactionManager());*/
Proxy.save("456","4444");
}
}
动态代理
实现方式:
- 针对真实类有接口使用JDK动态代理
- 针对真实类没实现接口使用CGLIB或者Javassist组件
实现机制,静态代理还是需要java文件经过编译,产生字节码文件(二进制)再通过类加载器加载到java虚拟机中,而动态代理,我们在运行期期间,遵循java编译.class文件的格式和结构,生成对应的二进制数据,直接将二进制转换成对应的类(大概理解其实就是跳过了编译生成字节码文件,直接写二进制转成对应的类)
JDK动态代理
JDK动态代理API
java.lang.reflect.Proxy
:Java 动态代理机制生成的所有动态代理类的父类
//为指定类加载器、一组接口及调用处理器生成动态代理类实例。
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler hanlder)
loader :类加载器
interfaces:代理类需要实现的接口
handler:代理执行处理器,想让代理对象帮我们做什么自己定义
返回:创建的代理对象。
java.lang.reflect.InvocationHandler
//让使用者自定义做什么事情
public Object invoke(Object proxy, Method method, Object[] args)
proxy :生成的代理对象;
method:当前调用的真实方法对象;
args :当前调用方法的实参。
返回:真实方法的返回结果。
代码实现:
- 创建业务层接口是实现类
public interface IUserService {
void save(String username,String password);}
---------------------------------------
public class UserServiceImpl implements IUserService {
//模拟
@Override
public void save(String username, String password) {
System.out.println("保存了"+username+""+password);}}
-
把事务代码抽取出来 ,跟上述静态一样
-
定义TransactionInvocationHandler实现
InvocationHandler
接口
java.lang.reflect //注意接口是这个包
public class TransactionInvocationHandler implements InvocationHandler {
//在这里告诉生成类的对象做什么事情,下面这个方法编写由开发者根据具体需求来决定
//调用真实对象的业务方法之前,开启事务,正常执行提交事务,若出现异常,回滚事务
//定义存真实对象的引用
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getTarget() {
return target;
}
private MyTransactionManager tx;
public void setTx(MyTransactionManager tx) {
this.tx = tx;
}
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
//反射调用真实对象的方法
Object reVal =null;
try {
tx.begin();
// 调用真实对象的方法
reVal=method.invoke(target, args);
tx.commit();
} catch (Exception e) {
tx.rollback();
}
return reVal;
}
}
- 修改applicationContext.xml配置文件
<bean id="tx" class="cn.k.tx.MyTransactionManager"/>
<bean id="UserHandler" class="cn.k.handle.TransactionInvocationHandler">
<property name="tx" ref="tx"/>
<property name="target">
<bean class="cn.k.service.impl.UserServiceImpl"/>
</property>
</bean>
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {
@Autowired
private TransactionInvocationHandler UserHandler;
@Test
public void save() {
/* TransactionInvocationHandler UserHandler = new TransactionInvocationHandler();
UserHandler.setTx(new MyTransactionManager());
UserHandler.setTarget(new UserServiceImpl()); //为哪个真实对象做代理*/
//帮我们动态生成代理类,并创建这个类的对象,最终底层会把我们从容器中获取到UserHandler对象
IUserService proxy = (IUserService)Proxy.newProxyInstance(
this.getClass().getClassLoader(),//类加载器
//生成类要实现的接口
UserHandler.getTarget().getClass().getInterfaces(),//获取真实类实现的接口,传递过去那么生成类就要实现相同的接口
UserHandler //指定生成类到底能做什么
);
//上面代码把实现类对象赋值给父类接口,下面调用方法会产生多态
proxy.save("456","789");
}
}
CGLIB 动态代理
API:
org.springframework.cglib.proxy.Enhancer
:类似 JDK 中 Proxy,用来生成代理类创建代理对象的。org.springframework.cglib.proxy.InvocationHandler
:类似 JDK 中 InvocationHandler,让使用者自定义做什么事情
CGLIB:代理类继承真实类
实现:
- 修改TransactionInvocationHandler类中的实现接口包名,改为
org.springframework.cglib.proxy.InvocationHandler
;其他不变 - 修改测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {
@Autowired
private TransactionInvocationHandler UserHandler;
@Test
public void save() {
Enhancer enhancer = new Enhancer();
//设置生成代理类继承的类
enhancer.setSuperclass(UserHandler.getTarget().getClass());
//设置生成代理类对象
enhancer.setCallback(UserHandler);
//生成代理类,创建代理对象
UserServiceImpl proxy = (UserServiceImpl)enhancer.create();
proxy.save("456","789");
}
}
注意与选用
JDK代理:代理类与真实类共同实现一个接口的。
CGLIB代理:代理类是继承真实类
JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承真实类的。
性能:Javassit > CGLIB > JDK。
关于JDK动态代理的原理
版本JDK11查看不了源码了,JDK8可以,ProxyGenerator 在 JDK 11 已经不是公有的了,所有emmm,我看不了,想想办法先